<commit_before>#include <string>
#include <stdio.h>
#include <tag.h>
#include <tstringlist.h>
#include <tbytevectorlist.h>
#include <tpropertymap.h>
#include <oggfile.h>
#include <vorbisfile.h>
#include <oggpageheader.h>
#include <cppunit/extensions/HelperMacros.h>
#include "utils.h"

using namespace std;
using namespace TagLib;

class TestOGG : public CppUnit::TestFixture
{
  CPPUNIT_TEST_SUITE(TestOGG);
  CPPUNIT_TEST(testSimple);
  CPPUNIT_TEST(testSplitPackets);
  CPPUNIT_TEST(testDictInterface1);
  CPPUNIT_TEST(testDictInterface2);
  CPPUNIT_TEST(testAudioProperties);
  CPPUNIT_TEST_SUITE_END();

public:

  void testSimple()
  {
    ScopedFileCopy copy("empty", ".ogg");
    string newname = copy.fileName();

    Vorbis::File *f = new Vorbis::File(newname.c_str());
    f->tag()->setArtist("The Artist");
    f->save();
    delete f;

    f = new Vorbis::File(newname.c_str());
    CPPUNIT_ASSERT_EQUAL(String("The Artist"), f->tag()->artist());
    delete f;
  }

  void testSplitPackets()
  {
    ScopedFileCopy copy("empty", ".ogg");
    string newname = copy.fileName();

    Vorbis::File *f = new Vorbis::File(newname.c_str());
    f->tag()->addField("test", ByteVector(128 * 1024, 'x') + ByteVector(1, '\0'));
    f->save();
    delete f;

    f = new Vorbis::File(newname.c_str());
    CPPUNIT_ASSERT_EQUAL(19, f->lastPageHeader()->pageSequenceNumber());
    delete f;
  }

  void testDictInterface1()
  {
    ScopedFileCopy copy("empty", ".ogg");
    string newname = copy.fileName();

    Vorbis::File *f = new Vorbis::File(newname.c_str());

    CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->tag()->properties().size());

    PropertyMap newTags;
    StringList values("value 1");
    values.append("value 2");
    newTags["ARTIST"] = values;
    f->tag()->setProperties(newTags);

    PropertyMap map = f->tag()->properties();
    CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), map.size());
    CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), map["ARTIST"].size());
    CPPUNIT_ASSERT_EQUAL(String("value 1"), map["ARTIST"][0]);
    delete f;

  }

  void testDictInterface2()
  {
    ScopedFileCopy copy("test", ".ogg");
    string newname = copy.fileName();

    Vorbis::File *f = new Vorbis::File(newname.c_str());
    PropertyMap tags = f->tag()->properties();

    CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), tags["UNUSUALTAG"].size());
    CPPUNIT_ASSERT_EQUAL(String("usual value"), tags["UNUSUALTAG"][0]);
    CPPUNIT_ASSERT_EQUAL(String("another value"), tags["UNUSUALTAG"][1]);
    CPPUNIT_ASSERT_EQUAL(
      String("\xC3\xB6\xC3\xA4\xC3\xBC\x6F\xCE\xA3\xC3\xB8", String::UTF8),
      tags["UNICODETAG"][0]);

    tags["UNICODETAG"][0] = String(
      "\xCE\xBD\xCE\xB5\xCF\x89\x20\xCE\xBD\xCE\xB1\xCE\xBB\xCF\x85\xCE\xB5", String::UTF8);
    tags.erase("UNUSUALTAG");
    f->tag()->setProperties(tags);
    CPPUNIT_ASSERT_EQUAL(
      String("\xCE\xBD\xCE\xB5\xCF\x89\x20\xCE\xBD\xCE\xB1\xCE\xBB\xCF\x85\xCE\xB5", String::UTF8),
      f->tag()->properties()["UNICODETAG"][0]);
    CPPUNIT_ASSERT_EQUAL(false, f->tag()->properties().contains("UNUSUALTAG"));

    delete f;
  }

  void testAudioProperties()
  {
    Ogg::Vorbis::File f(TEST_FILE_PATH_C("empty.ogg"));
    CPPUNIT_ASSERT(f.audioProperties());
    CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length());
    CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds());
    CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds());
    CPPUNIT_ASSERT_EQUAL(9, f.audioProperties()->bitrate());
    CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
    CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
    CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->vorbisVersion());
    CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMaximum());
    CPPUNIT_ASSERT_EQUAL(112000, f.audioProperties()->bitrateNominal());
    CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMinimum());
  }
};

CPPUNIT_TEST_SUITE_REGISTRATION(TestOGG);
<commit_msg>Improve a test about splitting OGG pages. Check for #529.<commit_after>#include <string>
#include <stdio.h>
#include <tag.h>
#include <tstringlist.h>
#include <tbytevectorlist.h>
#include <tpropertymap.h>
#include <oggfile.h>
#include <vorbisfile.h>
#include <oggpageheader.h>
#include <cppunit/extensions/HelperMacros.h>
#include "utils.h"

using namespace std;
using namespace TagLib;

class TestOGG : public CppUnit::TestFixture
{
  CPPUNIT_TEST_SUITE(TestOGG);
  CPPUNIT_TEST(testSimple);
  CPPUNIT_TEST(testSplitPackets);
  CPPUNIT_TEST(testDictInterface1);
  CPPUNIT_TEST(testDictInterface2);
  CPPUNIT_TEST(testAudioProperties);
  CPPUNIT_TEST_SUITE_END();

public:

  void testSimple()
  {
    ScopedFileCopy copy("empty", ".ogg");
    string newname = copy.fileName();

    Vorbis::File *f = new Vorbis::File(newname.c_str());
    f->tag()->setArtist("The Artist");
    f->save();
    delete f;

    f = new Vorbis::File(newname.c_str());
    CPPUNIT_ASSERT_EQUAL(String("The Artist"), f->tag()->artist());
    delete f;
  }

  void testSplitPackets()
  {
    ScopedFileCopy copy("empty", ".ogg");
    string newname = copy.fileName();

    String longText(std::string(128 * 1024, ' ').c_str());
    for(size_t i = 0; i < longText.length(); ++i)
      longText[i] = static_cast<wchar>(L'A' + (i % 26));

    Vorbis::File *f = new Vorbis::File(newname.c_str());
    f->tag()->setTitle(longText);
    f->save();
    delete f;

    f = new Vorbis::File(newname.c_str());
    CPPUNIT_ASSERT_EQUAL(19, f->lastPageHeader()->pageSequenceNumber());
    CPPUNIT_ASSERT_EQUAL(longText, f->tag()->title());
    delete f;
  }

  void testDictInterface1()
  {
    ScopedFileCopy copy("empty", ".ogg");
    string newname = copy.fileName();

    Vorbis::File *f = new Vorbis::File(newname.c_str());

    CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->tag()->properties().size());

    PropertyMap newTags;
    StringList values("value 1");
    values.append("value 2");
    newTags["ARTIST"] = values;
    f->tag()->setProperties(newTags);

    PropertyMap map = f->tag()->properties();
    CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), map.size());
    CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), map["ARTIST"].size());
    CPPUNIT_ASSERT_EQUAL(String("value 1"), map["ARTIST"][0]);
    delete f;

  }

  void testDictInterface2()
  {
    ScopedFileCopy copy("test", ".ogg");
    string newname = copy.fileName();

    Vorbis::File *f = new Vorbis::File(newname.c_str());
    PropertyMap tags = f->tag()->properties();

    CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), tags["UNUSUALTAG"].size());
    CPPUNIT_ASSERT_EQUAL(String("usual value"), tags["UNUSUALTAG"][0]);
    CPPUNIT_ASSERT_EQUAL(String("another value"), tags["UNUSUALTAG"][1]);
    CPPUNIT_ASSERT_EQUAL(
      String("\xC3\xB6\xC3\xA4\xC3\xBC\x6F\xCE\xA3\xC3\xB8", String::UTF8),
      tags["UNICODETAG"][0]);

    tags["UNICODETAG"][0] = String(
      "\xCE\xBD\xCE\xB5\xCF\x89\x20\xCE\xBD\xCE\xB1\xCE\xBB\xCF\x85\xCE\xB5", String::UTF8);
    tags.erase("UNUSUALTAG");
    f->tag()->setProperties(tags);
    CPPUNIT_ASSERT_EQUAL(
      String("\xCE\xBD\xCE\xB5\xCF\x89\x20\xCE\xBD\xCE\xB1\xCE\xBB\xCF\x85\xCE\xB5", String::UTF8),
      f->tag()->properties()["UNICODETAG"][0]);
    CPPUNIT_ASSERT_EQUAL(false, f->tag()->properties().contains("UNUSUALTAG"));

    delete f;
  }

  void testAudioProperties()
  {
    Ogg::Vorbis::File f(TEST_FILE_PATH_C("empty.ogg"));
    CPPUNIT_ASSERT(f.audioProperties());
    CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length());
    CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds());
    CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds());
    CPPUNIT_ASSERT_EQUAL(9, f.audioProperties()->bitrate());
    CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
    CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
    CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->vorbisVersion());
    CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMaximum());
    CPPUNIT_ASSERT_EQUAL(112000, f.audioProperties()->bitrateNominal());
    CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMinimum());
  }
};

CPPUNIT_TEST_SUITE_REGISTRATION(TestOGG);
<|endoftext|>


<commit_before>/*
 *  mkSVG.cpp
 *  MonkSVG
 *
 *  Created by Micah Pearlman on 8/2/10.
 *  Copyright 2010 Zero Vision. All rights reserved.
 *
 */

#include "mkSVG.h"
#include "tinyxml.h"
#include <map>
#include <iterator>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>
using namespace boost;

namespace MonkSVG {

	bool SVG::initialize( ISVGHandler* handler ) {
		_handler = handler;

		return true;
	}

	bool SVG::read( const char* data ) {
		TiXmlDocument doc;

		doc.Parse( data );

		TiXmlElement* root = doc.RootElement();//doc.FirstChild( "svg" )->ToElement();
		recursive_parse( &doc, root );

		return true;

	}

	bool SVG::read( string& data ) {
		return read( data.c_str() );
	}

	void SVG::recursive_parse( TiXmlDocument* doc, TiXmlElement* element ) {
		if ( element ) {
			TiXmlElement* child = element->FirstChildElement();
			recursive_parse( doc, child );
		}

		if ( element ) {
			for ( TiXmlElement* sibbling = element; sibbling != 0; sibbling = sibbling->NextSiblingElement() ) {
				string type = sibbling->Value();

				if ( type == "g" ) {
					handle_group( sibbling );
					// go through each child path
					//					recursive_parse( doc, sibbling->FirstChildElement() );
//					for ( TiXmlElement* child = sibbling->FirstChildElement(); child != 0; child = child->NextSiblingElement() ) {
//						handle_path( child );
//					}
				}

				if ( type == "path" ) {
					handle_path( sibbling );
				}
				if ( type == "rect" ) {
					handle_rect( sibbling );
				}
			}
		}
	}

	void SVG::handle_group( TiXmlElement* pathElement ) {
		_handler->onGroupBegin();

		// handle transform
		string transform;
		if ( pathElement->QueryStringAttribute( "transform", &transform) == TIXML_SUCCESS ) {
			parse_path_transform( transform );
		}

		// go through all the children
		TiXmlElement* children = pathElement->FirstChildElement();
		for ( TiXmlElement* child = children; child != 0; child = child->NextSiblingElement() ) {
			string type = child->Value();
			if ( type == "g" ) {
				handle_group( child );
			} else if( type == "path" ) {
				handle_path( child );
			} else 	if ( type == "rect" ) {
				handle_rect( child );
			}

		}

		_handler->onGroupEnd();

	}

	void SVG::handle_path( TiXmlElement* pathElement ) {
		_handler->onPathBegin();
		string d;
		if ( pathElement->QueryStringAttribute( "d", &d ) == TIXML_SUCCESS ) {
			parse_path_d( d );
		}

		handle_general_parameter( pathElement );

		_handler->onPathEnd();
	}

	void SVG::handle_rect( TiXmlElement* pathElement ) {
		_handler->onPathBegin();

		float pos[2];
		if ( pathElement->QueryFloatAttribute( "x", &pos[0] ) == TIXML_SUCCESS ) {
			//parse_path_d( d );
		}
		if ( pathElement->QueryFloatAttribute( "y", &pos[1] ) == TIXML_SUCCESS ) {
			//parse_path_d( d );
		}
		float sz[2];
		if ( pathElement->QueryFloatAttribute( "width", &sz[0] ) == TIXML_SUCCESS ) {
			//parse_path_d( d );
		}
		if ( pathElement->QueryFloatAttribute( "height", &sz[1] ) == TIXML_SUCCESS ) {
			//parse_path_d( d );
		}
		_handler->onPathRect( pos[0], pos[1], sz[0], sz[1] );

		handle_general_parameter( pathElement );

		_handler->onPathEnd();

	}

	void SVG::handle_general_parameter( TiXmlElement* pathElement ) {
		string fill;
		if ( pathElement->QueryStringAttribute( "fill", &fill ) == TIXML_SUCCESS ) {
			_handler->onPathFillColor( string_hex_color_to_uint( fill ) );
		}

		string stroke;
		if ( pathElement->QueryStringAttribute( "stroke", &stroke) == TIXML_SUCCESS ) {
			_handler->onPathStrokeColor( string_hex_color_to_uint( stroke ) );
		}

		string stroke_width;
		if ( pathElement->QueryStringAttribute( "stroke-width", &stroke_width) == TIXML_SUCCESS ) {
			float width = atof( stroke_width.c_str() );
			_handler->onPathStrokeWidth( width );
		}

		string style;
		if ( pathElement->QueryStringAttribute( "style", &style) == TIXML_SUCCESS ) {
			parse_path_style( style );
		}

		string transform;
		if ( pathElement->QueryStringAttribute( "transform", &transform) == TIXML_SUCCESS ) {
			parse_path_transform( transform );
		}
		string id_;
		if ( pathElement->QueryStringAttribute( "id", &id_) == TIXML_SUCCESS ) {
			_handler->onId( id_ );
			cout << id_ << endl;
		}

	}

	float SVG::d_string_to_float( char *c, char** str ) {
		while ( isspace(*c) ) {
			c++;
			(*str)++;
		}
		while ( *c == ',' ) {
			c++;
			(*str)++;
		}

		return strtof( c, str );
	}

	int SVG::d_string_to_int( char *c, char **str ) {
		while ( isspace(*c) ) {
			c++;
			(*str)++;
		}
		while ( *c == ',' ) {
			c++;
			(*str)++;
		}

		return strtol( c, str, 10);

	}

	uint32_t SVG::string_hex_color_to_uint( string& hexstring ) {
		uint32_t color = strtol( hexstring.c_str() + 1, 0, 16 );
		if ( hexstring.length() == 7 ) {	// fix up to rgba if the color is only rgb
			color = color << 8;
			color |= 0x000000ff;
		}

		return color;
	}

	void SVG::nextState( char** c, char* state ) {

		while ( isspace(**c) ) {
			if ( **c == '\0') {
				*state = 'e';
				return;
			}
			(*c)++;
		}
		if ( isalpha( **c ) ) {
			*state = **c;
			(*c)++;
			if ( **c == '\0') {
				*state = 'e';
				return;
			}

			if ( islower(*state) ) {	// if lower case then relative coords (see SVG spec)
				_handler->setRelative( true );
			} else {
				_handler->setRelative( false );
			}
		}

		//cout << "state: " << *state << endl;
	}

	void SVG::parse_path_transform( string& tr )	{
		size_t p = tr.find( "translate" );
		if ( p != string::npos ) {
			size_t left = tr.find( "(" );
			size_t right = tr.find( ")" );
			string values = tr.substr( left+1, right-1 );
			char* c = const_cast<char*>( values.c_str() );
			float x = d_string_to_float( c, &c );
			float y = d_string_to_float( c, &c );
			_handler->onTransformTranslate( x, y );
		} else if ( tr.find( "rotate" ) != string::npos ) {
			size_t left = tr.find( "(" );
			size_t right = tr.find( ")" );
			string values = tr.substr( left+1, right-1 );
			char* c = const_cast<char*>( values.c_str() );
			float a = d_string_to_float( c, &c );
			_handler->onTransformRotate( a );	// ??? radians or degrees ??
		} else if ( tr.find( "matrix" ) != string::npos ) {
			size_t left = tr.find( "(" );
			size_t right = tr.find( ")" );
			string values = tr.substr( left+1, right-1 );
			char* cc = const_cast<char*>( values.c_str() );
			float a = d_string_to_float( cc, &cc );
			float b = d_string_to_float( cc, &cc );
			float c = d_string_to_float( cc, &cc );
			float d = d_string_to_float( cc, &cc );
			float e = d_string_to_float( cc, &cc );
			float f = d_string_to_float( cc, &cc );
			_handler->onTransformMatrix( a, b, c, d, e, f );
		}
	}

	void SVG::parse_path_d( string& d ) {
		char* c = const_cast<char*>( d.c_str() );
		char state = *c;
		nextState( &c, &state );
		while ( *c && state != 'e' ) {

			switch ( state ) {
				case 'm':
				case 'M':
				{
					//c++;
					float x = d_string_to_float( c, &c );
					float y = d_string_to_float( c, &c );
					_handler->onPathMoveTo( x, y );
					nextState(&c, &state);
				}
				break;

				case 'l':
				case 'L':
				{
					//c++;
					float x = d_string_to_float( c, &c );
					float y = d_string_to_float( c, &c );
					_handler->onPathLineTo( x, y );
					nextState(&c, &state);

				}
				break;

				case 'c':
				case 'C':
				{
					//c++;
					float x1 = d_string_to_float( c, &c );
					float y1 = d_string_to_float( c, &c );
					float x2 = d_string_to_float( c, &c );
					float y2 = d_string_to_float( c, &c );
					float x3 = d_string_to_float( c, &c );
					float y3 = d_string_to_float( c, &c );
					_handler->onPathCubic(x1, y1, x2, y2, x3, y3);
					nextState(&c, &state);

				}
				break;

				case 'a':
				case 'A':
				{
					float rx = d_string_to_float( c, &c );
					float ry = d_string_to_float( c, &c );
					float x_axis_rotation = d_string_to_float( c, &c );
					int large_arc_flag = d_string_to_int( c, &c );
					int sweep_flag = d_string_to_int( c, &c );
					float x = d_string_to_float( c, &c );;
					float y = d_string_to_float( c, &c );
					_handler->onPathArc( rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, x, y );
					nextState(&c, &state);

				}
				break;

				case 'z':
				case 'Z':
				{
					//c++;
					_handler->onPathClose();
					nextState(&c, &state);

				}
				break;

				default:
					c++;
					break;
			}
		}
	}

	// semicolon-separated property declarations of the form "name : value" within the ‘style’ attribute
	void SVG::parse_path_style( string& ps ) {
		map< string, string > style_key_values;
		char_separator<char> values_seperator(";");
		char_separator<char> key_value_seperator(":");
		tokenizer<char_separator<char> > values_tokens( ps, values_seperator );
		BOOST_FOREACH( string values, values_tokens ) {
			tokenizer<char_separator<char> > key_value_tokens( values, key_value_seperator );
			tokenizer<char_separator<char> >::iterator k = key_value_tokens.begin();
			tokenizer<char_separator<char> >::iterator v = k;
			v++;
			//cout << *k << ":" << *v << endl;
			style_key_values[*k] = *v;
		}

		map<string, string>::iterator kv = style_key_values.find( string("fill") );
		if ( kv != style_key_values.end() ) {
			if( kv->second != "none" )
				_handler->onPathFillColor( string_hex_color_to_uint( kv->second ) );
		}

		kv = style_key_values.find( "stroke" );
		if ( kv != style_key_values.end() ) {
			if( kv->second != "none" )
				_handler->onPathStrokeColor( string_hex_color_to_uint( kv->second ) );
		}

		kv = style_key_values.find( "stroke-width" );
		if ( kv != style_key_values.end() ) {
			float width = atof( kv->second.c_str() );
			_handler->onPathStrokeWidth( width );
		}
	}

};<commit_msg>added arena collision<commit_after>/*
 *  mkSVG.cpp
 *  MonkSVG
 *
 *  Created by Micah Pearlman on 8/2/10.
 *  Copyright 2010 Zero Vision. All rights reserved.
 *
 */

#include "mkSVG.h"
#include "tinyxml.h"
#include <map>
#include <iterator>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>
using namespace boost;

namespace MonkSVG {

	bool SVG::initialize( ISVGHandler* handler ) {
		_handler = handler;

		return true;
	}

	bool SVG::read( const char* data ) {
		TiXmlDocument doc;

		doc.Parse( data );

		TiXmlElement* root = doc.RootElement();//doc.FirstChild( "svg" )->ToElement();
		recursive_parse( &doc, root );

		return true;

	}

	bool SVG::read( string& data ) {
		return read( data.c_str() );
	}

	void SVG::recursive_parse( TiXmlDocument* doc, TiXmlElement* element ) {
		if ( element ) {
			TiXmlElement* child = element->FirstChildElement();
			recursive_parse( doc, child );
		}

		if ( element ) {
			for ( TiXmlElement* sibbling = element; sibbling != 0; sibbling = sibbling->NextSiblingElement() ) {
				string type = sibbling->Value();

				if ( type == "g" ) {
					handle_group( sibbling );
					// go through each child path
					//					recursive_parse( doc, sibbling->FirstChildElement() );
//					for ( TiXmlElement* child = sibbling->FirstChildElement(); child != 0; child = child->NextSiblingElement() ) {
//						handle_path( child );
//					}
				}

				if ( type == "path" ) {
					handle_path( sibbling );
				}
				if ( type == "rect" ) {
					handle_rect( sibbling );
				}
			}
		}
	}

	void SVG::handle_group( TiXmlElement* pathElement ) {
		_handler->onGroupBegin();

		// handle transform
		string transform;
		if ( pathElement->QueryStringAttribute( "transform", &transform) == TIXML_SUCCESS ) {
			parse_path_transform( transform );
		}

		// go through all the children
		TiXmlElement* children = pathElement->FirstChildElement();
		for ( TiXmlElement* child = children; child != 0; child = child->NextSiblingElement() ) {
			string type = child->Value();
			if ( type == "g" ) {
				handle_group( child );
			} else if( type == "path" ) {
				handle_path( child );
			} else 	if ( type == "rect" ) {
				handle_rect( child );
			}

		}

		_handler->onGroupEnd();

	}

	void SVG::handle_path( TiXmlElement* pathElement ) {
		_handler->onPathBegin();
		string d;
		if ( pathElement->QueryStringAttribute( "d", &d ) == TIXML_SUCCESS ) {
			parse_path_d( d );
		}

		handle_general_parameter( pathElement );

		_handler->onPathEnd();
	}

	void SVG::handle_rect( TiXmlElement* pathElement ) {
		_handler->onPathBegin();

		float pos[2];
		if ( pathElement->QueryFloatAttribute( "x", &pos[0] ) == TIXML_SUCCESS ) {
			//parse_path_d( d );
		}
		if ( pathElement->QueryFloatAttribute( "y", &pos[1] ) == TIXML_SUCCESS ) {
			//parse_path_d( d );
		}
		float sz[2];
		if ( pathElement->QueryFloatAttribute( "width", &sz[0] ) == TIXML_SUCCESS ) {
			//parse_path_d( d );
		}
		if ( pathElement->QueryFloatAttribute( "height", &sz[1] ) == TIXML_SUCCESS ) {
			//parse_path_d( d );
		}
		_handler->onPathRect( pos[0], pos[1], sz[0], sz[1] );

		handle_general_parameter( pathElement );

		_handler->onPathEnd();

	}

	void SVG::handle_general_parameter( TiXmlElement* pathElement ) {
		string fill;
		if ( pathElement->QueryStringAttribute( "fill", &fill ) == TIXML_SUCCESS ) {
			_handler->onPathFillColor( string_hex_color_to_uint( fill ) );
		}

		string stroke;
		if ( pathElement->QueryStringAttribute( "stroke", &stroke) == TIXML_SUCCESS ) {
			_handler->onPathStrokeColor( string_hex_color_to_uint( stroke ) );
		}

		string stroke_width;
		if ( pathElement->QueryStringAttribute( "stroke-width", &stroke_width) == TIXML_SUCCESS ) {
			float width = atof( stroke_width.c_str() );
			_handler->onPathStrokeWidth( width );
		}

		string style;
		if ( pathElement->QueryStringAttribute( "style", &style) == TIXML_SUCCESS ) {
			parse_path_style( style );
		}

		string transform;
		if ( pathElement->QueryStringAttribute( "transform", &transform) == TIXML_SUCCESS ) {
			parse_path_transform( transform );
		}
		string id_;
		if ( pathElement->QueryStringAttribute( "id", &id_) == TIXML_SUCCESS ) {
			_handler->onId( id_ );
			//cout << id_ << endl;
		}

	}

	float SVG::d_string_to_float( char *c, char** str ) {
		while ( isspace(*c) ) {
			c++;
			(*str)++;
		}
		while ( *c == ',' ) {
			c++;
			(*str)++;
		}

		return strtof( c, str );
	}

	int SVG::d_string_to_int( char *c, char **str ) {
		while ( isspace(*c) ) {
			c++;
			(*str)++;
		}
		while ( *c == ',' ) {
			c++;
			(*str)++;
		}

		return strtol( c, str, 10);

	}

	uint32_t SVG::string_hex_color_to_uint( string& hexstring ) {
		uint32_t color = strtol( hexstring.c_str() + 1, 0, 16 );
		if ( hexstring.length() == 7 ) {	// fix up to rgba if the color is only rgb
			color = color << 8;
			color |= 0x000000ff;
		}

		return color;
	}

	void SVG::nextState( char** c, char* state ) {

		while ( isspace(**c) ) {
			if ( **c == '\0') {
				*state = 'e';
				return;
			}
			(*c)++;
		}
		if ( isalpha( **c ) ) {
			*state = **c;
			(*c)++;
			if ( **c == '\0') {
				*state = 'e';
				return;
			}

			if ( islower(*state) ) {	// if lower case then relative coords (see SVG spec)
				_handler->setRelative( true );
			} else {
				_handler->setRelative( false );
			}
		}

		//cout << "state: " << *state << endl;
	}

	void SVG::parse_path_transform( string& tr )	{
		size_t p = tr.find( "translate" );
		if ( p != string::npos ) {
			size_t left = tr.find( "(" );
			size_t right = tr.find( ")" );
			string values = tr.substr( left+1, right-1 );
			char* c = const_cast<char*>( values.c_str() );
			float x = d_string_to_float( c, &c );
			float y = d_string_to_float( c, &c );
			_handler->onTransformTranslate( x, y );
		} else if ( tr.find( "rotate" ) != string::npos ) {
			size_t left = tr.find( "(" );
			size_t right = tr.find( ")" );
			string values = tr.substr( left+1, right-1 );
			char* c = const_cast<char*>( values.c_str() );
			float a = d_string_to_float( c, &c );
			_handler->onTransformRotate( a );	// ??? radians or degrees ??
		} else if ( tr.find( "matrix" ) != string::npos ) {
			size_t left = tr.find( "(" );
			size_t right = tr.find( ")" );
			string values = tr.substr( left+1, right-1 );
			char* cc = const_cast<char*>( values.c_str() );
			float a = d_string_to_float( cc, &cc );
			float b = d_string_to_float( cc, &cc );
			float c = d_string_to_float( cc, &cc );
			float d = d_string_to_float( cc, &cc );
			float e = d_string_to_float( cc, &cc );
			float f = d_string_to_float( cc, &cc );
			_handler->onTransformMatrix( a, b, c, d, e, f );
		}
	}

	void SVG::parse_path_d( string& d ) {
		char* c = const_cast<char*>( d.c_str() );
		char state = *c;
		nextState( &c, &state );
		while ( *c && state != 'e' ) {

			switch ( state ) {
				case 'm':
				case 'M':
				{
					//c++;
					float x = d_string_to_float( c, &c );
					float y = d_string_to_float( c, &c );
					_handler->onPathMoveTo( x, y );
					nextState(&c, &state);
				}
				break;

				case 'l':
				case 'L':
				{
					//c++;
					float x = d_string_to_float( c, &c );
					float y = d_string_to_float( c, &c );
					_handler->onPathLineTo( x, y );
					nextState(&c, &state);

				}
				break;

				case 'c':
				case 'C':
				{
					//c++;
					float x1 = d_string_to_float( c, &c );
					float y1 = d_string_to_float( c, &c );
					float x2 = d_string_to_float( c, &c );
					float y2 = d_string_to_float( c, &c );
					float x3 = d_string_to_float( c, &c );
					float y3 = d_string_to_float( c, &c );
					_handler->onPathCubic(x1, y1, x2, y2, x3, y3);
					nextState(&c, &state);

				}
				break;

				case 'a':
				case 'A':
				{
					float rx = d_string_to_float( c, &c );
					float ry = d_string_to_float( c, &c );
					float x_axis_rotation = d_string_to_float( c, &c );
					int large_arc_flag = d_string_to_int( c, &c );
					int sweep_flag = d_string_to_int( c, &c );
					float x = d_string_to_float( c, &c );;
					float y = d_string_to_float( c, &c );
					_handler->onPathArc( rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, x, y );
					nextState(&c, &state);

				}
				break;

				case 'z':
				case 'Z':
				{
					//c++;
					_handler->onPathClose();
					nextState(&c, &state);

				}
				break;

				default:
					c++;
					break;
			}
		}
	}

	// semicolon-separated property declarations of the form "name : value" within the ‘style’ attribute
	void SVG::parse_path_style( string& ps ) {
		map< string, string > style_key_values;
		char_separator<char> values_seperator(";");
		char_separator<char> key_value_seperator(":");
		tokenizer<char_separator<char> > values_tokens( ps, values_seperator );
		BOOST_FOREACH( string values, values_tokens ) {
			tokenizer<char_separator<char> > key_value_tokens( values, key_value_seperator );
			tokenizer<char_separator<char> >::iterator k = key_value_tokens.begin();
			tokenizer<char_separator<char> >::iterator v = k;
			v++;
			//cout << *k << ":" << *v << endl;
			style_key_values[*k] = *v;
		}

		map<string, string>::iterator kv = style_key_values.find( string("fill") );
		if ( kv != style_key_values.end() ) {
			if( kv->second != "none" )
				_handler->onPathFillColor( string_hex_color_to_uint( kv->second ) );
		}

		kv = style_key_values.find( "stroke" );
		if ( kv != style_key_values.end() ) {
			if( kv->second != "none" )
				_handler->onPathStrokeColor( string_hex_color_to_uint( kv->second ) );
		}

		kv = style_key_values.find( "stroke-width" );
		if ( kv != style_key_values.end() ) {
			float width = atof( kv->second.c_str() );
			_handler->onPathStrokeWidth( width );
		}
	}

};<|endoftext|>


<commit_before>/*
 *  mkSVG.cpp
 *  MonkSVG
 *
 *  Created by Micah Pearlman on 8/2/10.
 *  Copyright 2010 Zero Vision. All rights reserved.
 *
 */

#include "mkSVG.h"
#include "tinyxml.h"
#include <map>
#include <iterator>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>
using namespace boost;

namespace MonkSVG {

	bool SVG::initialize( ISVGHandler* handler ) {
		_handler = handler;

		return true;
	}

	bool SVG::read( const char* data ) {
		TiXmlDocument doc;

		doc.Parse( data );
		TiXmlElement* root = doc.FirstChild( "svg" )->ToElement();
		recursive_parse( &doc, root );

		return true;

	}

	bool SVG::read( string& data ) {
		return read( data.c_str() );
	}

	void SVG::recursive_parse( TiXmlDocument* doc, TiXmlElement* element ) {
		if ( element ) {
			TiXmlElement* child = element->FirstChildElement();
			recursive_parse( doc, child );
		}

		if ( element ) {
			for ( TiXmlElement* sibbling = element; sibbling != 0; sibbling = sibbling->NextSiblingElement() ) {
				string type = sibbling->Value();

				if ( type == "g" ) {
					// go through each child path
					for ( TiXmlElement* child = sibbling->FirstChildElement(); child != 0; child = child->NextSiblingElement() ) {
						handle_path( child );
					}
				}

				if ( type == "path" ) {
					handle_path( sibbling );
				}
			}
		}
	}

	float SVG::d_string_to_float( char *c, char** str ) {
		while ( isspace(*c) ) {
			c++;
			(*str)++;
		}
		while ( *c == ',' ) {
			c++;
			(*str)++;
		}

		return strtof( c, str );
	}

	uint32_t SVG::string_hex_color_to_uint( string& hexstring ) {
		uint32_t color = strtol( hexstring.c_str() + 1, 0, 16 );
		if ( hexstring.length() == 7 ) {	// fix up to rgba if the color is only rgb
			color = color << 8;
			color |= 0x000000ff;
		}

		return color;
	}

	void SVG::handle_path( TiXmlElement* pathElement ) {
		_handler->onPathBegin();
		string d;
		if ( pathElement->QueryStringAttribute( "d", &d ) == TIXML_SUCCESS ) {
			parse_path_d( d );
		}

		string fill;
		if ( pathElement->QueryStringAttribute( "fill", &fill ) == TIXML_SUCCESS ) {
			_handler->onPathFillColor( string_hex_color_to_uint( fill ) );
		}

		string stroke;
		if ( pathElement->QueryStringAttribute( "stroke", &stroke) == TIXML_SUCCESS ) {
			_handler->onPathStrokeColor( string_hex_color_to_uint( stroke ) );
		}

		string stroke_width;
		if ( pathElement->QueryStringAttribute( "stroke-width", &stroke_width) == TIXML_SUCCESS ) {
			float width = atof( stroke_width.c_str() );
			_handler->onPathStrokeWidth( width );
		}

		string style;
		if ( pathElement->QueryStringAttribute( "style", &style) == TIXML_SUCCESS ) {
			parse_path_style( style );
		}

		string transform;
		if ( pathElement->QueryStringAttribute( "transform", &transform) == TIXML_SUCCESS ) {
			parse_path_transform( transform );
		}

		_handler->onPathEnd();
	}

	void SVG::nextState( char** c, char* state ) {

		while ( isspace(**c) ) {
			(*c)++;
		}
		if ( isalpha( **c ) ) {
			*state = **c;
			(*c)++;
			if ( islower(*state) ) {	// if lower case then relative coords (see SVG spec)
				_handler->setRelative( true );
			} else {
				_handler->setRelative( false );
			}

		}
	}

	void SVG::parse_path_transform( string& tr )	{
		size_t p = tr.find( "translate" );
		if ( p != string::npos ) {
			size_t left = tr.find( "(" );
			size_t right = tr.find( ")" );
			string values = tr.substr( left+1, right-1 );
			char* c = const_cast<char*>( values.c_str() );
			float x = d_string_to_float( c, &c );
			float y = d_string_to_float( c, &c );
			_handler->onTransformTranslate( x, y );
		} else if ( tr.find( "rotate" ) != string::npos ) {
			size_t left = tr.find( "(" );
			size_t right = tr.find( ")" );
			string values = tr.substr( left+1, right-1 );
			char* c = const_cast<char*>( values.c_str() );
			float a = d_string_to_float( c, &c );
			_handler->onTransformRotate( a );	// ??? radians or degrees ??
		} else if ( tr.find( "matrix" ) != string::npos ) {
			size_t left = tr.find( "(" );
			size_t right = tr.find( ")" );
			string values = tr.substr( left+1, right-1 );
			char* cc = const_cast<char*>( values.c_str() );
			float a = d_string_to_float( cc, &cc );
			float b = d_string_to_float( cc, &cc );
			float c = d_string_to_float( cc, &cc );
			float d = d_string_to_float( cc, &cc );
			float e = d_string_to_float( cc, &cc );
			float f = d_string_to_float( cc, &cc );
			_handler->onTransformMatrix( a, b, c, d, e, f );
		}
	}

	void SVG::parse_path_d( string& d ) {
		char* c = const_cast<char*>( d.c_str() );
		char state = *c;
		nextState( &c, &state );
		while ( *c && state != 'z' ) {

			switch ( state ) {
				case 'm':
				case 'M':
				{
					//c++;
					float x = d_string_to_float( c, &c );
					float y = d_string_to_float( c, &c );
					_handler->onPathMoveTo( x, y );
					nextState(&c, &state);
				}
				break;

				case 'l':
				case 'L':
				{
					//c++;
					float x = d_string_to_float( c, &c );
					float y = d_string_to_float( c, &c );
					_handler->onPathLineTo( x, y );
					nextState(&c, &state);

				}
				break;

				case 'c':
				case 'C':
				{
					//c++;
					float x1 = d_string_to_float( c, &c );
					float y1 = d_string_to_float( c, &c );
					float x2 = d_string_to_float( c, &c );
					float y2 = d_string_to_float( c, &c );
					float x3 = d_string_to_float( c, &c );
					float y3 = d_string_to_float( c, &c );
					_handler->onPathCubic(x1, y1, x2, y2, x3, y3);
					nextState(&c, &state);

				}
				break;
				case 'z':
				case 'Z':
				{
					//c++;
					_handler->onPathEnd();
					nextState(&c, &state);

				}
				break;

				default:
					c++;
					break;
			}
		}
	}

	// semicolon-separated property declarations of the form "name : value" within the ‘style’ attribute
	void SVG::parse_path_style( string& ps ) {
		map< string, string > style_key_values;
		char_separator<char> values_seperator(";");
		char_separator<char> key_value_seperator(":");
		tokenizer<char_separator<char> > values_tokens( ps, values_seperator );
		BOOST_FOREACH( string values, values_tokens ) {
			tokenizer<char_separator<char> > key_value_tokens( values, key_value_seperator );
			tokenizer<char_separator<char> >::iterator k = key_value_tokens.begin();
			tokenizer<char_separator<char> >::iterator v = k;
			v++;
			//cout << *k << ":" << *v << endl;
			style_key_values[*k] = *v;
		}

		map<string, string>::iterator kv = style_key_values.find( string("fill") );
		if ( kv != style_key_values.end() ) {
			_handler->onPathFillColor( string_hex_color_to_uint( kv->second ) );
		}

		kv = style_key_values.find( "stroke" );
		if ( kv != style_key_values.end() ) {
			_handler->onPathStrokeColor( string_hex_color_to_uint( kv->second ) );
		}

		kv = style_key_values.find( "stroke-width" );
		if ( kv != style_key_values.end() ) {
			float width = atof( kv->second.c_str() );
			_handler->onPathStrokeWidth( width );
		}
	}

};<commit_msg>fixed issue when there was multiple sub-countours in a path.  I.e. d = "M ... z M ... z M ... z"<commit_after>/*
 *  mkSVG.cpp
 *  MonkSVG
 *
 *  Created by Micah Pearlman on 8/2/10.
 *  Copyright 2010 Zero Vision. All rights reserved.
 *
 */

#include "mkSVG.h"
#include "tinyxml.h"
#include <map>
#include <iterator>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>
using namespace boost;

namespace MonkSVG {

	bool SVG::initialize( ISVGHandler* handler ) {
		_handler = handler;

		return true;
	}

	bool SVG::read( const char* data ) {
		TiXmlDocument doc;

		doc.Parse( data );
		TiXmlElement* root = doc.FirstChild( "svg" )->ToElement();
		recursive_parse( &doc, root );

		return true;

	}

	bool SVG::read( string& data ) {
		return read( data.c_str() );
	}

	void SVG::recursive_parse( TiXmlDocument* doc, TiXmlElement* element ) {
		if ( element ) {
			TiXmlElement* child = element->FirstChildElement();
			recursive_parse( doc, child );
		}

		if ( element ) {
			for ( TiXmlElement* sibbling = element; sibbling != 0; sibbling = sibbling->NextSiblingElement() ) {
				string type = sibbling->Value();

				if ( type == "g" ) {
					// go through each child path
					for ( TiXmlElement* child = sibbling->FirstChildElement(); child != 0; child = child->NextSiblingElement() ) {
						handle_path( child );
					}
				}

				if ( type == "path" ) {
					handle_path( sibbling );
				}
			}
		}
	}

	float SVG::d_string_to_float( char *c, char** str ) {
		while ( isspace(*c) ) {
			c++;
			(*str)++;
		}
		while ( *c == ',' ) {
			c++;
			(*str)++;
		}

		return strtof( c, str );
	}

	uint32_t SVG::string_hex_color_to_uint( string& hexstring ) {
		uint32_t color = strtol( hexstring.c_str() + 1, 0, 16 );
		if ( hexstring.length() == 7 ) {	// fix up to rgba if the color is only rgb
			color = color << 8;
			color |= 0x000000ff;
		}

		return color;
	}

	void SVG::handle_path( TiXmlElement* pathElement ) {
		_handler->onPathBegin();
		string d;
		if ( pathElement->QueryStringAttribute( "d", &d ) == TIXML_SUCCESS ) {
			parse_path_d( d );
		}

		string fill;
		if ( pathElement->QueryStringAttribute( "fill", &fill ) == TIXML_SUCCESS ) {
			_handler->onPathFillColor( string_hex_color_to_uint( fill ) );
		}

		string stroke;
		if ( pathElement->QueryStringAttribute( "stroke", &stroke) == TIXML_SUCCESS ) {
			_handler->onPathStrokeColor( string_hex_color_to_uint( stroke ) );
		}

		string stroke_width;
		if ( pathElement->QueryStringAttribute( "stroke-width", &stroke_width) == TIXML_SUCCESS ) {
			float width = atof( stroke_width.c_str() );
			_handler->onPathStrokeWidth( width );
		}

		string style;
		if ( pathElement->QueryStringAttribute( "style", &style) == TIXML_SUCCESS ) {
			parse_path_style( style );
		}

		string transform;
		if ( pathElement->QueryStringAttribute( "transform", &transform) == TIXML_SUCCESS ) {
			parse_path_transform( transform );
		}

		_handler->onPathEnd();
	}

	void SVG::nextState( char** c, char* state ) {

		while ( isspace(**c) ) {
			if ( **c == '\0') {
				*state = 'e';
				return;
			}
			(*c)++;
		}
		if ( isalpha( **c ) ) {
			*state = **c;
			(*c)++;
			if ( **c == '\0') {
				*state = 'e';
				return;
			}

			if ( islower(*state) ) {	// if lower case then relative coords (see SVG spec)
				_handler->setRelative( true );
			} else {
				_handler->setRelative( false );
			}
		}

		cout << "state: " << *state << endl;
	}

	void SVG::parse_path_transform( string& tr )	{
		size_t p = tr.find( "translate" );
		if ( p != string::npos ) {
			size_t left = tr.find( "(" );
			size_t right = tr.find( ")" );
			string values = tr.substr( left+1, right-1 );
			char* c = const_cast<char*>( values.c_str() );
			float x = d_string_to_float( c, &c );
			float y = d_string_to_float( c, &c );
			_handler->onTransformTranslate( x, y );
		} else if ( tr.find( "rotate" ) != string::npos ) {
			size_t left = tr.find( "(" );
			size_t right = tr.find( ")" );
			string values = tr.substr( left+1, right-1 );
			char* c = const_cast<char*>( values.c_str() );
			float a = d_string_to_float( c, &c );
			_handler->onTransformRotate( a );	// ??? radians or degrees ??
		} else if ( tr.find( "matrix" ) != string::npos ) {
			size_t left = tr.find( "(" );
			size_t right = tr.find( ")" );
			string values = tr.substr( left+1, right-1 );
			char* cc = const_cast<char*>( values.c_str() );
			float a = d_string_to_float( cc, &cc );
			float b = d_string_to_float( cc, &cc );
			float c = d_string_to_float( cc, &cc );
			float d = d_string_to_float( cc, &cc );
			float e = d_string_to_float( cc, &cc );
			float f = d_string_to_float( cc, &cc );
			_handler->onTransformMatrix( a, b, c, d, e, f );
		}
	}

	void SVG::parse_path_d( string& d ) {
		char* c = const_cast<char*>( d.c_str() );
		char state = *c;
		nextState( &c, &state );
		while ( *c && state != 'e' ) {

			switch ( state ) {
				case 'm':
				case 'M':
				{
					//c++;
					float x = d_string_to_float( c, &c );
					float y = d_string_to_float( c, &c );
					_handler->onPathMoveTo( x, y );
					nextState(&c, &state);
				}
				break;

				case 'l':
				case 'L':
				{
					//c++;
					float x = d_string_to_float( c, &c );
					float y = d_string_to_float( c, &c );
					_handler->onPathLineTo( x, y );
					nextState(&c, &state);

				}
				break;

				case 'c':
				case 'C':
				{
					//c++;
					float x1 = d_string_to_float( c, &c );
					float y1 = d_string_to_float( c, &c );
					float x2 = d_string_to_float( c, &c );
					float y2 = d_string_to_float( c, &c );
					float x3 = d_string_to_float( c, &c );
					float y3 = d_string_to_float( c, &c );
					_handler->onPathCubic(x1, y1, x2, y2, x3, y3);
					nextState(&c, &state);

				}
				break;
				case 'z':
				case 'Z':
				{
					//c++;
					_handler->onPathEnd();
					nextState(&c, &state);

				}
				break;

				default:
					c++;
					break;
			}
		}
	}

	// semicolon-separated property declarations of the form "name : value" within the ‘style’ attribute
	void SVG::parse_path_style( string& ps ) {
		map< string, string > style_key_values;
		char_separator<char> values_seperator(";");
		char_separator<char> key_value_seperator(":");
		tokenizer<char_separator<char> > values_tokens( ps, values_seperator );
		BOOST_FOREACH( string values, values_tokens ) {
			tokenizer<char_separator<char> > key_value_tokens( values, key_value_seperator );
			tokenizer<char_separator<char> >::iterator k = key_value_tokens.begin();
			tokenizer<char_separator<char> >::iterator v = k;
			v++;
			//cout << *k << ":" << *v << endl;
			style_key_values[*k] = *v;
		}

		map<string, string>::iterator kv = style_key_values.find( string("fill") );
		if ( kv != style_key_values.end() ) {
			_handler->onPathFillColor( string_hex_color_to_uint( kv->second ) );
		}

		kv = style_key_values.find( "stroke" );
		if ( kv != style_key_values.end() ) {
			_handler->onPathStrokeColor( string_hex_color_to_uint( kv->second ) );
		}

		kv = style_key_values.find( "stroke-width" );
		if ( kv != style_key_values.end() ) {
			float width = atof( kv->second.c_str() );
			_handler->onPathStrokeWidth( width );
		}
	}

};<|endoftext|>


<commit_before>/*
Copyright libCellML Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include "libcellml/model.h"

#include <algorithm>
#include <fstream>
#include <map>
#include <sstream>
#include <stack>
#include <utility>
#include <vector>

#include "libcellml/component.h"
#include "libcellml/importsource.h"
#include "libcellml/parser.h"
#include "libcellml/units.h"
#include "libcellml/variable.h"

#include "utilities.h"

namespace libcellml {

/**
 * @brief The Model::ModelImpl struct.
 *
 * This struct is the private implementation struct for the Model class.  Separating
 * the implementation from the definition allows for greater flexibility when
 * distributing the code.
 */
struct Model::ModelImpl
{
    std::vector<UnitsPtr> mUnits;

    std::vector<UnitsPtr>::iterator findUnits(const std::string &name);
    std::vector<UnitsPtr>::iterator findUnits(const UnitsPtr &units);
};

std::vector<UnitsPtr>::iterator Model::ModelImpl::findUnits(const std::string &name)
{
    return std::find_if(mUnits.begin(), mUnits.end(),
                        [=](const UnitsPtr &u) -> bool { return u->name() == name; });
}

std::vector<UnitsPtr>::iterator Model::ModelImpl::findUnits(const UnitsPtr &units)
{
    return std::find_if(mUnits.begin(), mUnits.end(),
                        [=](const UnitsPtr &u) -> bool { return units->name().empty() ? false : u->name() == units->name(); });
}

Model::Model()
    : mPimpl(new ModelImpl())
{
}

Model::Model(const std::string &name)
    : mPimpl(new ModelImpl())
{
    setName(name);
}

ModelPtr Model::create() noexcept
{
    return std::shared_ptr<Model> {new Model {}};
}

ModelPtr Model::create(const std::string &name) noexcept
{
    return std::shared_ptr<Model> {new Model {name}};
}

Model::~Model()
{
    delete mPimpl;
}

bool Model::doAddComponent(const ComponentPtr &component)
{
    if (component->hasParent()) {
        auto parent = component->parent();
        removeComponentFromEntity(parent, component);
    }
    component->setParent(shared_from_this());
    return ComponentEntity::doAddComponent(component);
}

void Model::addUnits(const UnitsPtr &units)
{
    mPimpl->mUnits.push_back(units);
    units->setParent(shared_from_this());
}

bool Model::removeUnits(size_t index)
{
    bool status = false;
    if (index < mPimpl->mUnits.size()) {
        auto units = *(mPimpl->mUnits.begin() + int64_t(index));
        units->removeParent();
        mPimpl->mUnits.erase(mPimpl->mUnits.begin() + int64_t(index));
        status = true;
    }

    return status;
}

bool Model::removeUnits(const std::string &name)
{
    bool status = false;
    auto result = mPimpl->findUnits(name);
    if (result != mPimpl->mUnits.end()) {
        (*result)->removeParent();
        mPimpl->mUnits.erase(result);
        status = true;
    }

    return status;
}

bool Model::removeUnits(const UnitsPtr &units)
{
    bool status = false;
    auto result = mPimpl->findUnits(units);
    if (result != mPimpl->mUnits.end()) {
        units->removeParent();
        mPimpl->mUnits.erase(result);
        status = true;
    }

    return status;
}

void Model::removeAllUnits()
{
    mPimpl->mUnits.clear();
}

bool Model::hasUnits(const std::string &name) const
{
    return mPimpl->findUnits(name) != mPimpl->mUnits.end();
}

bool Model::hasUnits(const UnitsPtr &units) const
{
    return mPimpl->findUnits(units) != mPimpl->mUnits.end();
}

UnitsPtr Model::units(size_t index) const
{
    UnitsPtr units = nullptr;
    if (index < mPimpl->mUnits.size()) {
        units = mPimpl->mUnits.at(index);
    }

    return units;
}

UnitsPtr Model::units(const std::string &name) const
{
    UnitsPtr units = nullptr;
    auto result = mPimpl->findUnits(name);
    if (result != mPimpl->mUnits.end()) {
        units = *result;
    }

    return units;
}

UnitsPtr Model::takeUnits(size_t index)
{
    UnitsPtr units = nullptr;
    if (index < mPimpl->mUnits.size()) {
        units = mPimpl->mUnits.at(index);
        removeUnits(index);
        units->removeParent();
    }

    return units;
}

UnitsPtr Model::takeUnits(const std::string &name)
{
    return takeUnits(size_t(mPimpl->findUnits(name) - mPimpl->mUnits.begin()));
}

bool Model::replaceUnits(size_t index, const UnitsPtr &units)
{
    bool status = false;
    if (removeUnits(index)) {
        mPimpl->mUnits.insert(mPimpl->mUnits.begin() + int64_t(index), units);
        status = true;
    }

    return status;
}

bool Model::replaceUnits(const std::string &name, const UnitsPtr &units)
{
    return replaceUnits(size_t(mPimpl->findUnits(name) - mPimpl->mUnits.begin()), units);
}

bool Model::replaceUnits(const UnitsPtr &oldUnits, const UnitsPtr &newUnits)
{
    return replaceUnits(size_t(mPimpl->findUnits(oldUnits) - mPimpl->mUnits.begin()), newUnits);
}

size_t Model::unitsCount() const
{
    return mPimpl->mUnits.size();
}

/**
 * @brief Resolve the path of the given filename using the given base.
 *
 * Resolves the full path to the given @p filename using the @p base.
 *
 * This function is only intended to work with local files.  It may not
 * work with bases that use the 'file://' prefix.
 *
 * @param filename The @c std::string relative path from the base path.
 * @param base The @c std::string location on local disk for determining the full path from.
 * @return The full path from the @p base location to the @p filename
 */
std::string resolvePath(const std::string &filename, const std::string &base)
{
    // We can be naive here as we know what we are dealing with
    std::string path = base.substr(0, base.find_last_of('/') + 1) + filename;
    return path;
}

void resolveImport(const ImportedEntityPtr &importedEntity,
                   const std::string &baseFile)
{
    if (importedEntity->isImport()) {
        ImportSourcePtr importSource = importedEntity->importSource();
        if (!importSource->hasModel()) {
            std::string url = resolvePath(importSource->url(), baseFile);
            std::ifstream file(url);
            if (file.good()) {
                std::stringstream buffer;
                buffer << file.rdbuf();
                ParserPtr parser = Parser::create();
                ModelPtr model = parser->parseModel(buffer.str());
                importSource->setModel(model);
                model->resolveImports(url);
            }
        }
    }
}

void resolveComponentImports(const ComponentEntityPtr &parentComponentEntity,
                             const std::string &baseFile)
{
    for (size_t n = 0; n < parentComponentEntity->componentCount(); ++n) {
        libcellml::ComponentPtr component = parentComponentEntity->component(n);
        if (component->isImport()) {
            resolveImport(component, baseFile);
        } else {
            resolveComponentImports(component, baseFile);
        }
    }
}

void Model::resolveImports(const std::string &baseFile)
{
    for (size_t n = 0; n < unitsCount(); ++n) {
        libcellml::UnitsPtr units = Model::units(n);
        resolveImport(units, baseFile);
    }
    resolveComponentImports(shared_from_this(), baseFile);
}

bool isUnresolvedImport(const ImportedEntityPtr &importedEntity)
{
    bool unresolvedImport = false;
    if (importedEntity->isImport()) {
        ImportSourcePtr importedSource = importedEntity->importSource();
        if (!importedSource->hasModel()) {
            unresolvedImport = true;
        }
    }
    return unresolvedImport;
}

bool hasUnresolvedComponentImports(const ComponentEntityPtr &parentComponentEntity);

bool doHasUnresolvedComponentImports(const ComponentPtr &component)
{
    bool unresolvedImports = false;
    if (component->isImport()) {
        unresolvedImports = isUnresolvedImport(component);
        if (!unresolvedImports) {
            // Check that the imported component can import all it needs from its model.
            ImportSourcePtr importedSource = component->importSource();
            if (importedSource->hasModel()) {
                ModelPtr importedModel = importedSource->model();
                ComponentPtr importedComponent = importedModel->component(component->importReference());
                unresolvedImports = doHasUnresolvedComponentImports(importedComponent);
            }
        }
    } else {
        unresolvedImports = hasUnresolvedComponentImports(component);
    }
    return unresolvedImports;
}

bool hasUnresolvedComponentImports(const ComponentEntityPtr &parentComponentEntity)
{
    bool unresolvedImports = false;
    for (size_t n = 0; n < parentComponentEntity->componentCount() && !unresolvedImports; ++n) {
        libcellml::ComponentPtr component = parentComponentEntity->component(n);
        unresolvedImports = doHasUnresolvedComponentImports(component);
    }
    return unresolvedImports;
}

bool Model::hasUnresolvedImports()
{
    bool unresolvedImports = false;
    for (size_t n = 0; n < unitsCount() && !unresolvedImports; ++n) {
        libcellml::UnitsPtr units = Model::units(n);
        unresolvedImports = isUnresolvedImport(units);
    }
    if (!unresolvedImports) {
        unresolvedImports = hasUnresolvedComponentImports(shared_from_this());
    }
    return unresolvedImports;
}

using IndexStack = std::vector<size_t>; /**< Type definition for tracking indicies. */
using EquivalenceMap = std::map<IndexStack, std::vector<IndexStack>>; /**< Type definition for map of variable equivalences defined over model. */

size_t getComponentIndexInComponentEntity(const ComponentEntityPtr &componentParent, const ComponentEntityPtr &component)
{
    size_t index = 0;
    bool found = false;
    while (index < componentParent->componentCount() && !found) {
        if (componentParent->component(index) == component) {
            found = true;
        } else {
            ++index;
        }
    }

    return index;
}

IndexStack reverseEngineerIndexStack(const VariablePtr &variable)
{
    IndexStack indexStack;
    ComponentPtr component = std::dynamic_pointer_cast<Component>(variable->parent());
    indexStack.push_back(getVariableIndexInComponent(component, variable));

    ComponentEntityPtr parent = component;
    ComponentEntityPtr grandParent = std::dynamic_pointer_cast<ComponentEntity>(parent->parent());
    while (grandParent != nullptr) {
        indexStack.push_back(getComponentIndexInComponentEntity(grandParent, parent));
        parent = grandParent;
        grandParent = std::dynamic_pointer_cast<ComponentEntity>(grandParent->parent());
    }

    std::reverse(std::begin(indexStack), std::end(indexStack));

    return indexStack;
}

void recordVariableEquivalences(const ComponentPtr &component, EquivalenceMap &equivalenceMap, IndexStack &indexStack)
{
    for (size_t index = 0; index < component->variableCount(); ++index) {
        auto variable = component->variable(index);
        for (size_t j = 0; j < variable->equivalentVariableCount(); ++j) {
            if (j == 0) {
                indexStack.push_back(index);
            }
            auto equivalentVariable = variable->equivalentVariable(j);
            auto equivalentVariableIndexStack = reverseEngineerIndexStack(equivalentVariable);
            if (equivalenceMap.count(indexStack) == 0) {
                equivalenceMap[indexStack] = std::vector<IndexStack>();
            }
            equivalenceMap[indexStack].push_back(equivalentVariableIndexStack);
        }
        if (variable->equivalentVariableCount() > 0) {
            indexStack.pop_back();
        }
    }
}

void generateEquivalenceMap(const ComponentPtr &component, EquivalenceMap &map, IndexStack &indexStack)
{
    for (size_t index = 0; index < component->componentCount(); ++index) {
        indexStack.push_back(index);
        auto c = component->component(index);
        recordVariableEquivalences(c, map, indexStack);
        generateEquivalenceMap(c, map, indexStack);
        indexStack.pop_back();
    }
}

EquivalenceMap generateEquivalenceMap(const std::shared_ptr<const libcellml::Model> &model)
{
    EquivalenceMap map;

    IndexStack indexStack;
    for (size_t index = 0; index < model->componentCount(); ++index) {
        indexStack.push_back(index);
        auto c = model->component(index);
        recordVariableEquivalences(c, map, indexStack);
        generateEquivalenceMap(c, map, indexStack);
        indexStack.pop_back();
    }

    return map;
}

VariablePtr getVariableLocatedAt(const IndexStack &stack, const ModelPtr &model)
{
    ComponentPtr component;
    for (size_t index = 0; index < stack.size() - 1; ++index) {
        if (index == 0) {
            component = model->component(stack.at(index));
        } else {
            component = component->component(stack.at(index));
        }
    }

    return component->variable(stack.back());
}

void makeEquivalence(const IndexStack &stack1, const IndexStack &stack2, const ModelPtr &model)
{
    auto v1 = getVariableLocatedAt(stack1, model);
    auto v2 = getVariableLocatedAt(stack2, model);
    Variable::addEquivalence(v1, v2);
}

void applyEquivalenceMapToModel(const EquivalenceMap &map, const ModelPtr &model)
{
    for (const auto &iter : map) {
        auto key = iter.first;
        auto vector = iter.second;
        for (auto vectorIter = vector.begin(); vectorIter < vector.end(); ++vectorIter) {
            makeEquivalence(key, *vectorIter, model);
        }
    }
}

ModelPtr Model::clone() const
{
    auto m = create();

    m->setId(id());
    m->setName(name());

    m->setEncapsulationId(encapsulationId());

    for (size_t index = 0; index < mPimpl->mUnits.size(); ++index) {
        m->addUnits(units(index)->clone());
    }

    for (size_t index = 0; index < componentCount(); ++index) {
        m->addComponent(component(index)->clone());
    }

    auto map = generateEquivalenceMap(shared_from_this());
    applyEquivalenceMapToModel(map, m);

    return m;
}

} // namespace libcellml
<commit_msg>Simplify equivalence map creation slightly.<commit_after>/*
Copyright libCellML Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include "libcellml/model.h"

#include <algorithm>
#include <fstream>
#include <map>
#include <sstream>
#include <stack>
#include <utility>
#include <vector>

#include "libcellml/component.h"
#include "libcellml/importsource.h"
#include "libcellml/parser.h"
#include "libcellml/units.h"
#include "libcellml/variable.h"

#include "utilities.h"

namespace libcellml {

/**
 * @brief The Model::ModelImpl struct.
 *
 * This struct is the private implementation struct for the Model class.  Separating
 * the implementation from the definition allows for greater flexibility when
 * distributing the code.
 */
struct Model::ModelImpl
{
    std::vector<UnitsPtr> mUnits;

    std::vector<UnitsPtr>::iterator findUnits(const std::string &name);
    std::vector<UnitsPtr>::iterator findUnits(const UnitsPtr &units);
};

std::vector<UnitsPtr>::iterator Model::ModelImpl::findUnits(const std::string &name)
{
    return std::find_if(mUnits.begin(), mUnits.end(),
                        [=](const UnitsPtr &u) -> bool { return u->name() == name; });
}

std::vector<UnitsPtr>::iterator Model::ModelImpl::findUnits(const UnitsPtr &units)
{
    return std::find_if(mUnits.begin(), mUnits.end(),
                        [=](const UnitsPtr &u) -> bool { return units->name().empty() ? false : u->name() == units->name(); });
}

Model::Model()
    : mPimpl(new ModelImpl())
{
}

Model::Model(const std::string &name)
    : mPimpl(new ModelImpl())
{
    setName(name);
}

ModelPtr Model::create() noexcept
{
    return std::shared_ptr<Model> {new Model {}};
}

ModelPtr Model::create(const std::string &name) noexcept
{
    return std::shared_ptr<Model> {new Model {name}};
}

Model::~Model()
{
    delete mPimpl;
}

bool Model::doAddComponent(const ComponentPtr &component)
{
    if (component->hasParent()) {
        auto parent = component->parent();
        removeComponentFromEntity(parent, component);
    }
    component->setParent(shared_from_this());
    return ComponentEntity::doAddComponent(component);
}

void Model::addUnits(const UnitsPtr &units)
{
    mPimpl->mUnits.push_back(units);
    units->setParent(shared_from_this());
}

bool Model::removeUnits(size_t index)
{
    bool status = false;
    if (index < mPimpl->mUnits.size()) {
        auto units = *(mPimpl->mUnits.begin() + int64_t(index));
        units->removeParent();
        mPimpl->mUnits.erase(mPimpl->mUnits.begin() + int64_t(index));
        status = true;
    }

    return status;
}

bool Model::removeUnits(const std::string &name)
{
    bool status = false;
    auto result = mPimpl->findUnits(name);
    if (result != mPimpl->mUnits.end()) {
        (*result)->removeParent();
        mPimpl->mUnits.erase(result);
        status = true;
    }

    return status;
}

bool Model::removeUnits(const UnitsPtr &units)
{
    bool status = false;
    auto result = mPimpl->findUnits(units);
    if (result != mPimpl->mUnits.end()) {
        units->removeParent();
        mPimpl->mUnits.erase(result);
        status = true;
    }

    return status;
}

void Model::removeAllUnits()
{
    mPimpl->mUnits.clear();
}

bool Model::hasUnits(const std::string &name) const
{
    return mPimpl->findUnits(name) != mPimpl->mUnits.end();
}

bool Model::hasUnits(const UnitsPtr &units) const
{
    return mPimpl->findUnits(units) != mPimpl->mUnits.end();
}

UnitsPtr Model::units(size_t index) const
{
    UnitsPtr units = nullptr;
    if (index < mPimpl->mUnits.size()) {
        units = mPimpl->mUnits.at(index);
    }

    return units;
}

UnitsPtr Model::units(const std::string &name) const
{
    UnitsPtr units = nullptr;
    auto result = mPimpl->findUnits(name);
    if (result != mPimpl->mUnits.end()) {
        units = *result;
    }

    return units;
}

UnitsPtr Model::takeUnits(size_t index)
{
    UnitsPtr units = nullptr;
    if (index < mPimpl->mUnits.size()) {
        units = mPimpl->mUnits.at(index);
        removeUnits(index);
        units->removeParent();
    }

    return units;
}

UnitsPtr Model::takeUnits(const std::string &name)
{
    return takeUnits(size_t(mPimpl->findUnits(name) - mPimpl->mUnits.begin()));
}

bool Model::replaceUnits(size_t index, const UnitsPtr &units)
{
    bool status = false;
    if (removeUnits(index)) {
        mPimpl->mUnits.insert(mPimpl->mUnits.begin() + int64_t(index), units);
        status = true;
    }

    return status;
}

bool Model::replaceUnits(const std::string &name, const UnitsPtr &units)
{
    return replaceUnits(size_t(mPimpl->findUnits(name) - mPimpl->mUnits.begin()), units);
}

bool Model::replaceUnits(const UnitsPtr &oldUnits, const UnitsPtr &newUnits)
{
    return replaceUnits(size_t(mPimpl->findUnits(oldUnits) - mPimpl->mUnits.begin()), newUnits);
}

size_t Model::unitsCount() const
{
    return mPimpl->mUnits.size();
}

/**
 * @brief Resolve the path of the given filename using the given base.
 *
 * Resolves the full path to the given @p filename using the @p base.
 *
 * This function is only intended to work with local files.  It may not
 * work with bases that use the 'file://' prefix.
 *
 * @param filename The @c std::string relative path from the base path.
 * @param base The @c std::string location on local disk for determining the full path from.
 * @return The full path from the @p base location to the @p filename
 */
std::string resolvePath(const std::string &filename, const std::string &base)
{
    // We can be naive here as we know what we are dealing with
    std::string path = base.substr(0, base.find_last_of('/') + 1) + filename;
    return path;
}

void resolveImport(const ImportedEntityPtr &importedEntity,
                   const std::string &baseFile)
{
    if (importedEntity->isImport()) {
        ImportSourcePtr importSource = importedEntity->importSource();
        if (!importSource->hasModel()) {
            std::string url = resolvePath(importSource->url(), baseFile);
            std::ifstream file(url);
            if (file.good()) {
                std::stringstream buffer;
                buffer << file.rdbuf();
                ParserPtr parser = Parser::create();
                ModelPtr model = parser->parseModel(buffer.str());
                importSource->setModel(model);
                model->resolveImports(url);
            }
        }
    }
}

void resolveComponentImports(const ComponentEntityPtr &parentComponentEntity,
                             const std::string &baseFile)
{
    for (size_t n = 0; n < parentComponentEntity->componentCount(); ++n) {
        libcellml::ComponentPtr component = parentComponentEntity->component(n);
        if (component->isImport()) {
            resolveImport(component, baseFile);
        } else {
            resolveComponentImports(component, baseFile);
        }
    }
}

void Model::resolveImports(const std::string &baseFile)
{
    for (size_t n = 0; n < unitsCount(); ++n) {
        libcellml::UnitsPtr units = Model::units(n);
        resolveImport(units, baseFile);
    }
    resolveComponentImports(shared_from_this(), baseFile);
}

bool isUnresolvedImport(const ImportedEntityPtr &importedEntity)
{
    bool unresolvedImport = false;
    if (importedEntity->isImport()) {
        ImportSourcePtr importedSource = importedEntity->importSource();
        if (!importedSource->hasModel()) {
            unresolvedImport = true;
        }
    }
    return unresolvedImport;
}

bool hasUnresolvedComponentImports(const ComponentEntityPtr &parentComponentEntity);

bool doHasUnresolvedComponentImports(const ComponentPtr &component)
{
    bool unresolvedImports = false;
    if (component->isImport()) {
        unresolvedImports = isUnresolvedImport(component);
        if (!unresolvedImports) {
            // Check that the imported component can import all it needs from its model.
            ImportSourcePtr importedSource = component->importSource();
            if (importedSource->hasModel()) {
                ModelPtr importedModel = importedSource->model();
                ComponentPtr importedComponent = importedModel->component(component->importReference());
                unresolvedImports = doHasUnresolvedComponentImports(importedComponent);
            }
        }
    } else {
        unresolvedImports = hasUnresolvedComponentImports(component);
    }
    return unresolvedImports;
}

bool hasUnresolvedComponentImports(const ComponentEntityPtr &parentComponentEntity)
{
    bool unresolvedImports = false;
    for (size_t n = 0; n < parentComponentEntity->componentCount() && !unresolvedImports; ++n) {
        libcellml::ComponentPtr component = parentComponentEntity->component(n);
        unresolvedImports = doHasUnresolvedComponentImports(component);
    }
    return unresolvedImports;
}

bool Model::hasUnresolvedImports()
{
    bool unresolvedImports = false;
    for (size_t n = 0; n < unitsCount() && !unresolvedImports; ++n) {
        libcellml::UnitsPtr units = Model::units(n);
        unresolvedImports = isUnresolvedImport(units);
    }
    if (!unresolvedImports) {
        unresolvedImports = hasUnresolvedComponentImports(shared_from_this());
    }
    return unresolvedImports;
}

using IndexStack = std::vector<size_t>; /**< Type definition for tracking indicies. */
using EquivalenceMap = std::map<IndexStack, std::vector<IndexStack>>; /**< Type definition for map of variable equivalences defined over model. */

size_t getComponentIndexInComponentEntity(const ComponentEntityPtr &componentParent, const ComponentEntityPtr &component)
{
    size_t index = 0;
    bool found = false;
    while (index < componentParent->componentCount() && !found) {
        if (componentParent->component(index) == component) {
            found = true;
        } else {
            ++index;
        }
    }

    return index;
}

IndexStack reverseEngineerIndexStack(const VariablePtr &variable)
{
    IndexStack indexStack;
    ComponentPtr component = std::dynamic_pointer_cast<Component>(variable->parent());
    indexStack.push_back(getVariableIndexInComponent(component, variable));

    ComponentEntityPtr parent = component;
    ComponentEntityPtr grandParent = std::dynamic_pointer_cast<ComponentEntity>(parent->parent());
    while (grandParent != nullptr) {
        indexStack.push_back(getComponentIndexInComponentEntity(grandParent, parent));
        parent = grandParent;
        grandParent = std::dynamic_pointer_cast<ComponentEntity>(grandParent->parent());
    }

    std::reverse(std::begin(indexStack), std::end(indexStack));

    return indexStack;
}

void recordVariableEquivalences(const ComponentPtr &component, EquivalenceMap &equivalenceMap, IndexStack &indexStack)
{
    for (size_t index = 0; index < component->variableCount(); ++index) {
        auto variable = component->variable(index);
        for (size_t j = 0; j < variable->equivalentVariableCount(); ++j) {
            if (j == 0) {
                indexStack.push_back(index);
            }
            auto equivalentVariable = variable->equivalentVariable(j);
            auto equivalentVariableIndexStack = reverseEngineerIndexStack(equivalentVariable);
            if (equivalenceMap.count(indexStack) == 0) {
                equivalenceMap[indexStack] = std::vector<IndexStack>();
            }
            equivalenceMap[indexStack].push_back(equivalentVariableIndexStack);
        }
        if (variable->equivalentVariableCount() > 0) {
            indexStack.pop_back();
        }
    }
}

void generateEquivalenceMap(const ComponentPtr &component, EquivalenceMap &map, IndexStack &indexStack)
{
    for (size_t index = 0; index < component->componentCount(); ++index) {
        indexStack.push_back(index);
        auto c = component->component(index);
        recordVariableEquivalences(c, map, indexStack);
        generateEquivalenceMap(c, map, indexStack);
        indexStack.pop_back();
    }
}

VariablePtr getVariableLocatedAt(const IndexStack &stack, const ModelPtr &model)
{
    ComponentPtr component;
    for (size_t index = 0; index < stack.size() - 1; ++index) {
        if (index == 0) {
            component = model->component(stack.at(index));
        } else {
            component = component->component(stack.at(index));
        }
    }

    return component->variable(stack.back());
}

void makeEquivalence(const IndexStack &stack1, const IndexStack &stack2, const ModelPtr &model)
{
    auto v1 = getVariableLocatedAt(stack1, model);
    auto v2 = getVariableLocatedAt(stack2, model);
    Variable::addEquivalence(v1, v2);
}

void applyEquivalenceMapToModel(const EquivalenceMap &map, const ModelPtr &model)
{
    for (const auto &iter : map) {
        auto key = iter.first;
        auto vector = iter.second;
        for (auto vectorIter = vector.begin(); vectorIter < vector.end(); ++vectorIter) {
            makeEquivalence(key, *vectorIter, model);
        }
    }
}

ModelPtr Model::clone() const
{
    auto m = create();

    m->setId(id());
    m->setName(name());

    m->setEncapsulationId(encapsulationId());

    for (size_t index = 0; index < mPimpl->mUnits.size(); ++index) {
        m->addUnits(units(index)->clone());
    }

    for (size_t index = 0; index < componentCount(); ++index) {
        m->addComponent(component(index)->clone());
    }

    // Generate equivalence map starting from the models components.
    EquivalenceMap map;
    IndexStack indexStack;
    for (size_t index = 0; index < componentCount(); ++index) {
        indexStack.push_back(index);
        auto c = component(index);
        recordVariableEquivalences(c, map, indexStack);
        generateEquivalenceMap(c, map, indexStack);
        indexStack.pop_back();
    }
    applyEquivalenceMapToModel(map, m);

    return m;
}

} // namespace libcellml
<|endoftext|>


<commit_before>//=======================================================================
// Copyright Baptiste Wicht 2013.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================

#include "money.hpp"
#include "utils.hpp"
#include "budget_exception.hpp"

using namespace budget;

money budget::parse_money(const std::string& money_string){
    std::size_t dot_pos = money_string.find(".");

    int dollars = 0;
    int cents = 0;

    if(dot_pos == std::string::npos){
        dollars = to_number<int>(money_string);
    } else {
        dollars = to_number<int>(money_string.substr(0, dot_pos));

        auto cents_str = money_string.substr(dot_pos+1, money_string.size() - dot_pos);
        cents = to_number<int>(cents_str);
    }

    return {dollars, cents};
}

std::ostream& budget::operator<<(std::ostream& stream, const money& amount){
    if(amount.cents() < 10){
        if(amount.negative()){
            return stream << '-' << (-1 * amount.dollars()) << ".0" << amount.cents();
        } else {
            return stream << amount.dollars() << ".0" << amount.cents();
        }
    } else {
        if(amount.negative()){
            return stream << '-' << (-1 * amount.dollars()) << "." << amount.cents();
        } else {
            return stream << amount.dollars() << "." << amount.cents();
        }
    }
}

void budget::not_negative(const money& amount){
    if(amount.dollars() < 0 || amount.cents() < 0){
        throw budget_exception("Amount cannot be negative");
    }
}
<commit_msg>Improve error reporting<commit_after>//=======================================================================
// Copyright Baptiste Wicht 2013.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================

#include "money.hpp"
#include "utils.hpp"
#include "budget_exception.hpp"

using namespace budget;

money budget::parse_money(const std::string& money_string){
    std::size_t dot_pos = money_string.find(".");

    int dollars = 0;
    int cents = 0;

    try {
        if(dot_pos == std::string::npos){
            dollars = to_number<int>(money_string);
        } else {
            dollars = to_number<int>(money_string.substr(0, dot_pos));

            auto cents_str = money_string.substr(dot_pos+1, money_string.size() - dot_pos);
            cents = to_number<int>(cents_str);
        }
    } catch (boost::bad_lexical_cast& e){
        throw budget::budget_exception("\"" + money_string + "\" is not a valid amount of money");
    }

    return {dollars, cents};
}

std::ostream& budget::operator<<(std::ostream& stream, const money& amount){
    if(amount.cents() < 10){
        if(amount.negative()){
            return stream << '-' << (-1 * amount.dollars()) << ".0" << amount.cents();
        } else {
            return stream << amount.dollars() << ".0" << amount.cents();
        }
    } else {
        if(amount.negative()){
            return stream << '-' << (-1 * amount.dollars()) << "." << amount.cents();
        } else {
            return stream << amount.dollars() << "." << amount.cents();
        }
    }
}

void budget::not_negative(const money& amount){
    if(amount.dollars() < 0 || amount.cents() < 0){
        throw budget_exception("Amount cannot be negative");
    }
}
<|endoftext|>


<commit_before>#include <iostream>
#include <cstdlib>
#include <string>
#include <sstream>
#include <map>
#include <vector>
#include "curses.h"
#include "string.h"

#include "../csv_data_manipulator.hpp"

using namespace std;

enum {
    CSV_CMD_READ = 1,

    CSV_CMD_END
};

void remove_multiple_spaces(std::string &str_command)
{
    std::string search_1("  ");
    std::string search_2("\t");
    size_t idx;

    while ( (idx = str_command.find(search_2)) != std::string::npos ) {
        str_command.replace(idx, 1, " ");
    }

    while ( (idx = str_command.find(search_1)) != std::string::npos ) {
        str_command.erase(idx, 1);
    }
}

bool add_item_to_map(std::map<std::string, int> &command_map, std::string key, int value)
{
    std::pair<std::map<std::string, int>::iterator, bool> ret;
    ret = command_map.insert(std::pair<std::string, int>(key, value));
    return ret.second;
}

bool init_command_map(std::map<std::string, int> &command_map)
{
    if (!add_item_to_map(command_map, "read", CSV_CMD_READ)) return false;

    return true;
}

bool run_command(CSVData &main_data, std::map<std::string, int> &command_map, std::string command, std::string &output)
{
    output.clear();

    if (command.size() == 0) return true;

    std::vector<std::string> command_list;
    std::string command_part;

    std::stringstream total_command(command);

    while (std::getline(total_command, command_part, ' ')) {
         command_list.push_back(command_part);
    }

    std::map<std::string, int>::iterator cmd_code = command_map.find(command_list.at(0));

    if (cmd_code == command_map.end()) {
        output.assign("Invalid command '");
        output.append(command_list.at(0));
        output.append("'.");
    } else {
        output.assign("Valid command ");
        output.append(std::to_string(cmd_code->second));
    }

    return true;
}

void init_screen(CSVData &main_data)
{
    clear();
    printw(" ***********************************\n");
    printw(" *** CSV Data Manipulator %s  ***\n", main_data.get_version());
    printw(" *** type h for help             ***\n");
    printw(" *** type q to quit              ***\n");
    printw(" ***********************************\n\n");
}

int main(int argc, char *argv[])
{
    char        command[1024] = {'\0'};

    std::string output;
    CSVData     main_data;

    std::map<std::string, int> command_map;

    if (!init_command_map(command_map)) {
        std::cout << "Error: duplicated entry in command map initialization..." << std::endl;
        return EXIT_FAILURE;
    }

    initscr();
    init_screen(main_data);

    while (strcmp(command, "q") && strcmp(command, "quit")) {
        addstr(" csv command> ");
        refresh();
        getnstr(command, sizeof(command) - 1);

        std::string str_command(command);

        remove_multiple_spaces(str_command);

        if (!run_command(main_data, command_map, str_command, output)) {
             std::cout << "Error: could not execute command..." << std::endl;
             return EXIT_FAILURE;
        }

        init_screen(main_data);
        printw(" Response:  %s\n", output.c_str());
        refresh();
    }

    endwin();

    return EXIT_SUCCESS;
}
<commit_msg>Added console commands: show, version, delete<commit_after>#include <iostream>
#include <cstdlib>
#include <string>
#include <sstream>
#include <fstream>
#include <map>
#include <vector>
#include <algorithm>
#include <stdexcept>
#include "curses.h"
#include "string.h"

#include "../csv_data_manipulator.hpp"

using namespace std;

enum {
    CSV_CMD_READ = 1,
    CSV_CMD_SHOW,
    CSV_CMD_VERSION,
    CSV_CMD_DELETE,

    CSV_CMD_END
};

void remove_multiple_spaces(std::string &str_command)
{
    std::string search_1("  ");
    std::string search_2("\t");
    size_t idx;

    while ( (idx = str_command.find(search_2)) != std::string::npos ) {
        str_command.replace(idx, 1, " ");
    }

    while ( (idx = str_command.find(search_1)) != std::string::npos ) {
        str_command.erase(idx, 1);
    }
}

bool add_item_to_map(std::map<std::string, int> &command_map, std::string key, int value)
{
    std::pair<std::map<std::string, int>::iterator, bool> ret;
    ret = command_map.insert(std::pair<std::string, int>(key, value));
    return ret.second;
}

bool init_command_map(std::map<std::string, int> &command_map)
{
    if (!add_item_to_map(command_map, "read", CSV_CMD_READ)) return false;
    if (!add_item_to_map(command_map, "show", CSV_CMD_SHOW)) return false;
    if (!add_item_to_map(command_map, "version", CSV_CMD_VERSION)) return false;
    if (!add_item_to_map(command_map, "delete", CSV_CMD_DELETE)) return false;

    return true;
}

std::string execute_known_command(CSVData &main_data, int cmd_code, std::vector<std::string> command_list)
{
    std::string ret_val;
    switch (cmd_code) {
        case CSV_CMD_READ:
            if (command_list.size() > 1) {
                std::ifstream file(command_list[1]);
                if (file.good()) {
                    main_data.read_file(command_list[1]);
                    ret_val.assign("Reading from: '");
                    ret_val.append(command_list[1]);
                    ret_val.append("' completed!");
                } else {
                    ret_val.assign("Cannot access file.");
                }
            } else {
                ret_val.assign("No filename specified.");
            }
            break;
        case CSV_CMD_SHOW:
            if (command_list.size() > 1) {
                ret_val.clear();
                std::vector<std::string>::iterator it;
                if ( (it = std::find(command_list.begin(), command_list.end(), "rows")) != command_list.end()) {
                    ret_val.append("Rows: ");
                    ret_val.append(std::to_string(main_data.rows()));
                } else if ( (it = std::find(command_list.begin(), command_list.end(), "columns")) != command_list.end()) {
                    ret_val.append("Columns: ");
                    ret_val.append(std::to_string(main_data.columns()));
                } else if ( (it = std::find(command_list.begin(), command_list.end(), "row")) != command_list.end()) {
                    *it++;
                    std::string row_idx = *it;
                    int i_row_idx;
                    bool error = false;

                    try {
                        i_row_idx = std::stol(row_idx);
                    } catch (const invalid_argument& ia) {
                        error = true;
                    }

                    if (error) {
                         ret_val.append("Invalid row index.");
                    } else if (i_row_idx < 1 || i_row_idx > main_data.rows()) {
                         ret_val.append("Index out of range.");
                    } else {
                        ret_val.append("Row "+row_idx+" : ");
                        std::vector<std::string> row_data = main_data.get_row(i_row_idx - 1);
                        for (int i = 0; i < row_data.size(); i++) ret_val.append(row_data.at(i)+",");
                    }
                } else {
                    ret_val.assign("Invalid parameter.");
                }
            } else {
                ret_val.assign("Missing parameter...");
            }
            break;
        case CSV_CMD_DELETE:
            if (command_list.size() > 1) {
                ret_val.clear();
                std::vector<std::string>::iterator it;
                if ( (it = std::find(command_list.begin(), command_list.end(), "row")) != command_list.end()) {
                    *it++;
                    std::string row_idx = *it;
                    int i_row_idx;
                    bool error = false;

                    try {
                        i_row_idx = std::stol(row_idx);
                    } catch (const invalid_argument& ia) {
                        error = true;
                    }

                    if (error) {
                         ret_val.append("Invalid row index.");
                    } else if (i_row_idx < 1 || i_row_idx > main_data.rows()) {
                         ret_val.append("Index out of range.");
                    } else {
                        main_data.delete_row(i_row_idx - 1);
                        ret_val.append("Row "+row_idx+" deleted.");
                    }
                } else {
                    ret_val.assign("Invalid parameter.");
                }
            } else {
                ret_val.assign("Missing parameter...");
            }
            break;
        case CSV_CMD_VERSION:
            ret_val.assign(main_data.get_version());
            break;
        default:
            ret_val.assign("Invalid command.");
            break;
    }
    return ret_val;
}

bool run_command(CSVData &main_data, std::map<std::string, int> &command_map, std::string command, std::string &output)
{
    output.clear();

    if (command.size() == 0) return true;

    std::vector<std::string> command_list;
    std::string command_part;

    std::stringstream total_command(command);

    while (std::getline(total_command, command_part, ' ')) {
         command_list.push_back(command_part);
    }

    std::map<std::string, int>::iterator cmd_code = command_map.find(command_list.at(0));

    if (cmd_code == command_map.end()) {
        output.assign("Invalid command '");
        output.append(command_list.at(0));
        output.append("'.");
    } else {
        std::string exe_out = execute_known_command(main_data, cmd_code->second, command_list);
        output.assign(exe_out);
    }

    return true;
}

void init_screen(CSVData &main_data)
{
    clear();
    printw(" ***********************************\n");
    printw(" *** CSV Data Manipulator %s  ***\n", main_data.get_version());
    printw(" *** type h for help             ***\n");
    printw(" *** type q to quit              ***\n");
    printw(" ***********************************\n\n");
}

int main(int argc, char *argv[])
{
    char        command[1024] = {'\0'};

    std::string output;
    CSVData     main_data;

    std::map<std::string, int> command_map;

    if (!init_command_map(command_map)) {
        std::cout << "Error: duplicated entry in command map initialization..." << std::endl;
        return EXIT_FAILURE;
    }

    initscr();
    init_screen(main_data);

    while (strcmp(command, "q") && strcmp(command, "quit")) {
        addstr(" csv command> ");
        refresh();
        getnstr(command, sizeof(command) - 1);

        std::string str_command(command);

        remove_multiple_spaces(str_command);

        if (!run_command(main_data, command_map, str_command, output)) {
             std::cout << "Error: could not execute command..." << std::endl;
             return EXIT_FAILURE;
        }

        init_screen(main_data);
        printw(" Response:  %s\n", output.c_str());
        refresh();
    }

    endwin();

    return EXIT_SUCCESS;
}
<|endoftext|>


<commit_before>//=======================================================================
// Copyright (c) 2013-2018 Baptiste Wicht.
// Distributed under the terms of the MIT License.
// (See accompanying file LICENSE or copy at
//  http://opensource.org/licenses/MIT)
//=======================================================================

#include <iostream>

#include "retirement.hpp"
#include "assets.hpp"
#include "accounts.hpp"
#include "expenses.hpp"
#include "earnings.hpp"
#include "budget_exception.hpp"
#include "config.hpp"
#include "console.hpp"
#include "writer.hpp"

using namespace budget;

namespace {

constexpr size_t running_limit = 12;

money running_expenses(){
    auto today = budget::local_day();

    budget::date end = today - budget::days(today.day() - 1);
    budget::date start = end - budget::months(running_limit);

    budget::money total;

    for(auto& expense : all_expenses()){
        if(expense.date >= start && expense.date < end){
            total += expense.amount;
        }
    }

    return total;
}

double running_savings_rate(){
    auto today = budget::local_day();

    double savings_rate = 0.0;

    for(size_t i = 1; i <= running_limit; ++i){
        auto d = today - budget::months(i);

        budget::money expenses;

        for (auto& expense : all_expenses()) {
            if (expense.date.year() == d.year() && expense.date.month() == d.month()) {
                expenses += expense.amount;
            }
        }

        budget::money earnings;

        for (auto& earning : all_earnings()) {
            if (earning.date.year() == d.year() && earning.date.month() == d.month()) {
                earnings += earning.amount;
            }
        }

        budget::money income;

        for (auto& account : all_accounts(d.year(), d.month())){
            income += account.amount;
        }

        auto balance = income + earnings - expenses;
        auto local   = balance / (income + earnings);

        if(local < 0){
            local = 0;
        }

        savings_rate += local;
    }

    return savings_rate / running_limit;
}

void retirement_set() {
    double wrate = 4.0;
    double roi = 4.0;

    if(internal_config_contains("withdrawal_rate")){
        wrate = to_number<double>(internal_config_value("withdrawal_rate"));
    }

    if(internal_config_contains("expected_roi")){
        roi = to_number<double>(internal_config_value("expected_roi"));
    }

    edit_double(wrate, "Withdrawal Rate (%)");
    edit_double(roi, "Expected Annual Return (%)");

    // Save the configuration
    internal_config_value("withdrawal_rate") = to_string(wrate);
    internal_config_value("expected_roi") = to_string(roi);
}

} // end of anonymous namespace

void budget::retirement_module::load() {
    load_accounts();
    load_assets();
    load_expenses();
    load_earnings();
}

void budget::retirement_module::handle(std::vector<std::string>& args) {
    console_writer w(std::cout);

    if (args.empty() || args.size() == 1) {
        retirement_status(w);
    } else {
        auto& subcommand = args[1];

        if (subcommand == "status") {
            retirement_status(w);
        } else if (subcommand == "set") {
            retirement_set();
            std::cout << std::endl;
            retirement_status(w);
        } else {
            throw budget_exception("Invalid subcommand \"" + subcommand + "\"");
        }
    }
}

void budget::retirement_status(budget::writer& w) {
    if (!w.is_web()) {
        if (!internal_config_contains("withdrawal_rate")) {
            w << "Not enough information, please configure first with retirement set" << end_of_line;
            return;
        }

        if (!internal_config_contains("expected_roi")) {
            w << "Not enough information, please configure first with retirement set" << end_of_line;
            return;
        }
    }

    auto currency = get_default_currency();
    auto wrate = to_number<double>(internal_config_value("withdrawal_rate"));
    auto roi = to_number<double>(internal_config_value("expected_roi"));
    auto years = double(int(100.0 / wrate));
    auto expenses = running_expenses();
    auto savings_rate = running_savings_rate();
    auto nw = get_net_worth();
    auto missing = years * expenses - nw;
    auto income= 12 * get_base_income();
    auto a_savings_rate = (income - expenses) / income;

    size_t base_months = 0;
    size_t a_base_months = 0;

    auto current_nw = nw;
    while(current_nw < years * expenses){
        current_nw *= 1.0 + (roi / 100.0) / 12;
        current_nw += (savings_rate * income) / 12;

        ++base_months;
    }

    current_nw = nw;
    while(current_nw < years * expenses){
        current_nw *= 1.0 + (roi / 100.0) / 12;
        current_nw += (a_savings_rate * income) / 12;

        ++a_base_months;
    }

    std::vector<std::string> columns = {};
    std::vector<std::vector<std::string>> contents;

    using namespace std::string_literals;

    contents.push_back({"Withdrawal rate"s, to_string(wrate) + "%"});
    contents.push_back({"Years of expense"s, to_string(years)});
    contents.push_back({"Running expenses"s, to_string(expenses) + " " + currency});
    contents.push_back({"Target Net Worth"s, to_string(years * expenses) + " " + currency});
    contents.push_back({"Current Net Worth"s, to_string(nw) + " " + currency});
    contents.push_back({"Missing Net Worth"s, to_string(missing) + " " + currency});
    contents.push_back({"FI Ratio"s, to_string(100 * (nw / missing)) + "%"});
    contents.push_back({"Yearly income"s, to_string(income) + " " + currency});
    contents.push_back({"Running Savings Rate"s, to_string(100 * savings_rate) + "%"});
    contents.push_back({"Yearly savings"s, to_string(savings_rate * income) + " " + currency});
    contents.push_back({"Time to FI (w/o returns)"s, to_string(missing / (savings_rate * income)) + " years"});
    contents.push_back({"Time to FI (w/ returns)"s, to_string(base_months / 12.0) + " years"});

    contents.push_back({"Adjusted Savings Rate"s, to_string(100 * a_savings_rate) + "%"});
    contents.push_back({"Adjusted Yearly savings"s, to_string(a_savings_rate * income) + " " + currency});
    contents.push_back({"Adjusted Time to FI (w/o returns)"s, to_string(missing / (a_savings_rate * income)) + " years"});
    contents.push_back({"Adjusted Time to FI (w/ returns)"s, to_string(a_base_months / 12.0) + " years"});

    w.display_table(columns, contents);

    std::array<int, 5> rate_decs{1, 2, 5, 10, 20};

    // Note: this not totally correct since we ignore the
    // correlation between the savings rate and the expenses

    for (auto dec : rate_decs) {
        auto dec_savings_rate = savings_rate + 0.01 * dec;

        auto current_nw = nw;
        size_t months   = 0;

        while (current_nw < years * expenses) {
            current_nw *= 1.0 + (roi / 100.0) / 12;
            current_nw += (dec_savings_rate * income) / 12;

            ++months;
        }

        w << p_begin << "Increasing Savings Rate by " << dec << "% would save " << (base_months - months) / 12.0 << " years (in " << months / 12.0 << " years)" << p_end;
    }
}
<commit_msg>New scenario<commit_after>//=======================================================================
// Copyright (c) 2013-2018 Baptiste Wicht.
// Distributed under the terms of the MIT License.
// (See accompanying file LICENSE or copy at
//  http://opensource.org/licenses/MIT)
//=======================================================================

#include <iostream>

#include "retirement.hpp"
#include "assets.hpp"
#include "accounts.hpp"
#include "expenses.hpp"
#include "earnings.hpp"
#include "budget_exception.hpp"
#include "config.hpp"
#include "console.hpp"
#include "writer.hpp"

using namespace budget;

namespace {

constexpr size_t running_limit = 12;

money running_expenses(){
    auto today = budget::local_day();

    budget::date end = today - budget::days(today.day() - 1);
    budget::date start = end - budget::months(running_limit);

    budget::money total;

    for(auto& expense : all_expenses()){
        if(expense.date >= start && expense.date < end){
            total += expense.amount;
        }
    }

    return total;
}

double running_savings_rate(){
    auto today = budget::local_day();

    double savings_rate = 0.0;

    for(size_t i = 1; i <= running_limit; ++i){
        auto d = today - budget::months(i);

        budget::money expenses;

        for (auto& expense : all_expenses()) {
            if (expense.date.year() == d.year() && expense.date.month() == d.month()) {
                expenses += expense.amount;
            }
        }

        budget::money earnings;

        for (auto& earning : all_earnings()) {
            if (earning.date.year() == d.year() && earning.date.month() == d.month()) {
                earnings += earning.amount;
            }
        }

        budget::money income;

        for (auto& account : all_accounts(d.year(), d.month())){
            income += account.amount;
        }

        auto balance = income + earnings - expenses;
        auto local   = balance / (income + earnings);

        if(local < 0){
            local = 0;
        }

        savings_rate += local;
    }

    return savings_rate / running_limit;
}

void retirement_set() {
    double wrate = 4.0;
    double roi = 4.0;

    if(internal_config_contains("withdrawal_rate")){
        wrate = to_number<double>(internal_config_value("withdrawal_rate"));
    }

    if(internal_config_contains("expected_roi")){
        roi = to_number<double>(internal_config_value("expected_roi"));
    }

    edit_double(wrate, "Withdrawal Rate (%)");
    edit_double(roi, "Expected Annual Return (%)");

    // Save the configuration
    internal_config_value("withdrawal_rate") = to_string(wrate);
    internal_config_value("expected_roi") = to_string(roi);
}

} // end of anonymous namespace

void budget::retirement_module::load() {
    load_accounts();
    load_assets();
    load_expenses();
    load_earnings();
}

void budget::retirement_module::handle(std::vector<std::string>& args) {
    console_writer w(std::cout);

    if (args.empty() || args.size() == 1) {
        retirement_status(w);
    } else {
        auto& subcommand = args[1];

        if (subcommand == "status") {
            retirement_status(w);
        } else if (subcommand == "set") {
            retirement_set();
            std::cout << std::endl;
            retirement_status(w);
        } else {
            throw budget_exception("Invalid subcommand \"" + subcommand + "\"");
        }
    }
}

void budget::retirement_status(budget::writer& w) {
    if (!w.is_web()) {
        if (!internal_config_contains("withdrawal_rate")) {
            w << "Not enough information, please configure first with retirement set" << end_of_line;
            return;
        }

        if (!internal_config_contains("expected_roi")) {
            w << "Not enough information, please configure first with retirement set" << end_of_line;
            return;
        }
    }

    auto currency = get_default_currency();
    auto wrate = to_number<double>(internal_config_value("withdrawal_rate"));
    auto roi = to_number<double>(internal_config_value("expected_roi"));
    auto years = double(int(100.0 / wrate));
    auto expenses = running_expenses();
    auto savings_rate = running_savings_rate();
    auto nw = get_net_worth();
    auto missing = years * expenses - nw;
    auto income= 12 * get_base_income();
    auto a_savings_rate = (income - expenses) / income;

    size_t base_months = 0;
    size_t a_base_months = 0;

    auto current_nw = nw;
    while(current_nw < years * expenses){
        current_nw *= 1.0 + (roi / 100.0) / 12;
        current_nw += (savings_rate * income) / 12;

        ++base_months;
    }

    current_nw = nw;
    while(current_nw < years * expenses){
        current_nw *= 1.0 + (roi / 100.0) / 12;
        current_nw += (a_savings_rate * income) / 12;

        ++a_base_months;
    }

    std::vector<std::string> columns = {};
    std::vector<std::vector<std::string>> contents;

    using namespace std::string_literals;

    contents.push_back({"Withdrawal rate"s, to_string(wrate) + "%"});
    contents.push_back({"Years of expense"s, to_string(years)});
    contents.push_back({"Running expenses"s, to_string(expenses) + " " + currency});
    contents.push_back({"Target Net Worth"s, to_string(years * expenses) + " " + currency});
    contents.push_back({"Current Net Worth"s, to_string(nw) + " " + currency});
    contents.push_back({"Missing Net Worth"s, to_string(missing) + " " + currency});
    contents.push_back({"FI Ratio"s, to_string(100 * (nw / missing)) + "%"});
    contents.push_back({"Yearly income"s, to_string(income) + " " + currency});
    contents.push_back({"Running Savings Rate"s, to_string(100 * savings_rate) + "%"});
    contents.push_back({"Yearly savings"s, to_string(savings_rate * income) + " " + currency});
    contents.push_back({"Time to FI (w/o returns)"s, to_string(missing / (savings_rate * income)) + " years"});
    contents.push_back({"Time to FI (w/ returns)"s, to_string(base_months / 12.0) + " years"});

    contents.push_back({"Adjusted Savings Rate"s, to_string(100 * a_savings_rate) + "%"});
    contents.push_back({"Adjusted Yearly savings"s, to_string(a_savings_rate * income) + " " + currency});
    contents.push_back({"Adjusted Time to FI (w/o returns)"s, to_string(missing / (a_savings_rate * income)) + " years"});
    contents.push_back({"Adjusted Time to FI (w/ returns)"s, to_string(a_base_months / 12.0) + " years"});

    w.display_table(columns, contents);

    std::array<int, 5> rate_decs{1, 2, 5, 10, 20};

    // Note: this not totally correct since we ignore the
    // correlation between the savings rate and the expenses

    for (auto dec : rate_decs) {
        auto dec_savings_rate = savings_rate + 0.01 * dec;

        auto current_nw = nw;
        size_t months   = 0;

        while (current_nw < years * expenses) {
            current_nw *= 1.0 + (roi / 100.0) / 12;
            current_nw += (dec_savings_rate * income) / 12;

            ++months;
        }

        w << p_begin << "Increasing Savings Rate by " << dec << "% would save " << (base_months - months) / 12.0 << " years (in " << months / 12.0 << " years)" << p_end;
    }

    std::array<int, 5> exp_decs{10, 50, 100, 200, 500};

    for (auto dec : exp_decs) {
        auto current_nw = nw;
        size_t months   = 0;

        auto new_savings_rate = (income - (expenses - dec * 12)) / income;;

        while (current_nw < years * (expenses - (dec * 12))) {
            current_nw *= 1.0 + (roi / 100.0) / 12;
            current_nw += (new_savings_rate * income) / 12;

            ++months;
        }

        w << p_begin << "Decreasing monthly expenses by " << dec << " " << currency << " would save " << (base_months - months) / 12.0 << " years (in " << months / 12.0 << " (adjusted) years)" << p_end;
    }
}
<|endoftext|>