[
  {
    "path": ".gitignore",
    "content": "*.pyc\n*.pyo\n*.so\n*~\nbuild\ndist\nhtml\npyShipping.egg-info\n*.bak\n"
  },
  {
    "path": "CHANGES",
    "content": "1.9:    neue Routeninfos von DPD zum 2011-05-07\n1.8:    neue Routeninfos von DPD zum 2011-01-03\n1.7:    neue Routeninfos von DPD zum 6.9.2010\n1.6:    addressvalidation added\n1.5:    Keep Package dimensions sorted, buendelung(), __add__() and hat_gleiche_seiten() \n        added binpacking (c) David Pisinger, Silvano Martello, Daniele Vigo\n        and an independent pure Python implementation\n        unified PackageSize and Package, made Package sortable\n1.4:    Gemeinsame Basisklasse fuer Exceptions in pyshipping.carriers.dpd.georoute\n1.3p3:  Added new Routing Table for DPD (Routendatenbank Januar - April 2010)\n1.3p2:  Added new Routing Table for DPD\n1.3p1:  Provide extra argument basedir to fortras.bordero.ship(). Bordero files will be saved in this directory.\n"
  },
  {
    "path": "LICENSE",
    "content": "Original code in this disturibution is\nCopyright 2008, 2009 HUDORA GmbH. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are\npermitted provided that the following conditions are met:\n\n   1. Redistributions of source code must retain the above copyright notice, this list of\n      conditions and the following disclaimer.\n\n   2. Redistributions in binary form must reproduce the above copyright notice, this list\n      of conditions and the following disclaimer in the documentation and/or other materials\n      provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY HUDORA GmbH ``AS IS'' AND ANY EXPRESS OR IMPLIED\nWARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\nADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nThis distribution contains software by third parties which might be licensed differently.\nSee the respective sourcefiles for details."
  },
  {
    "path": "MANIFEST.in",
    "content": "include README.rst\ninclude pyshipping/carriers/dpd/georoutetables/*"
  },
  {
    "path": "Makefile",
    "content": "# setting the PATH seems only to work in GNUmake not in BSDmake\nPATH := ./testenv/bin:$(PATH)\n\ncheck:\n\tpep8 -r --ignore=E501 pyshipping/\n\tsh -c 'PYTHONPATH=. pyflakes pyshipping/'\n\t-sh -c 'PYTHONPATH=. pylint -iy --max-line-length=110 pyshipping/' # -rn\n\nbuild:\n\tpython setup.py build\n\ntest:\n\tPYTHONPATH=. python pyshipping/__init__.py # find import errors\n\tPYTHONPATH=. python pyshipping/shipment.py\n\tPYTHONPATH=. python pyshipping/package.py\n\tPYTHONPATH=. python pyshipping/fortras/test.py\n\tPYTHONPATH=. python pyshipping/binpack.py\n\t# These tests tend to fail because of routing table updates\n\tPYTHONPATH=. python pyshipping/carriers/dpd/georoute_test.py\n\ndependencies:\n\tvirtualenv testenv\n\tpip -q install -E testenv -r requirements.txt\n\nstatistics:\n\tsloccount --wide --details pyshipping | tee sloccount.sc\n\nupload: build doc\n\tpython setup.py sdist upload\n\ndoc: build\n\trm -Rf html\n\tmkdir -p html\n\tmkdir -p html/fortras\n\tmkdir -p html/carriers\n\tsh -c '(cd html; pydoc -w ../pyshipping/*.py)'\n\tsh -c '(cd html/fortras; pydoc -w ../../pyshipping/*.py)'\n\tsh -c '(cd html/carriers; pydoc -w ../../pyshipping/*.py)'\n\ninstall: build\n\tsudo python setup.py install\n\nclean:\n\trm -Rf testenv build dist html test.db pyShipping.egg-info pylint.out sloccount.sc pip-log.txt\n\tfind . -name '*.pyc' -or -name '*.pyo' -delete\n\n.PHONY: test build clean check upload doc install\n"
  },
  {
    "path": "README.rst",
    "content": "pyShipping provides connections to interface with shipping companies and to transport shipping related information. \n\n * package - shipping/cargo related calculations based on a unit of shipping (box, crate, package), includes\n   a bin packing implementation in pure Python\n * sendung - defines an abstract shippment (Sendung), with packages and calculations based on that\n * addressvalidation - check if an address is valid\n * carriers.dpd - calculation of DPD/Georoutes routing data and labels. Included tables are for shippments from Wuppertal but it should work with all other german routing tables. See this Blogpost_ about updating routing information.\n * fortras - tools for reading and writing Fortras messages. Fortras is a EDI standard for logistics related information somewhat common in Germany. See Wikipedia_ for further enlightenment\n\n.. _Wikipedia: http://de.wikipedia.org/wiki/Fortras\n.. _Blogpost: https://cybernetics.hudora.biz/intern/wordpress/2010/09/dpd-routeninformationen-aktualisieren/\n\nIt also comes with the only python based `3D Bin Packing <http://www.cs.sunysb.edu/~algorith/files/bin-packing.shtml>`_ implementation I'm aware of. The Algorithm has sufficient performance to be used in everyday shipping and warehousing applications.\n\nYou can get the whole Package at http://pypi.python.org/pypi/pyShipping\n\nThis code is BSD Licensed.\n\n.. image:: https://d2weczhvl823v0.cloudfront.net/hudora/pyshipping/trend.png\n   :alt: Bitdeli badge\n   :target: https://bitdeli.com/free\n\n"
  },
  {
    "path": "pyshipping/3dbpp.c",
    "content": "\n/* ======================================================================\n      3D BIN PACKING, Silvano Martello, David Pisinger, Daniele Vigo \n                             1998, 2003, 2006\n   ====================================================================== */\n\n/* This code solves the three-dimensional bin-packing problem, which\n * asks for an orthogonal packing of a given set of rectangular-shaped\n * boxes into the minimum number of three-dimensional rectangular bins.\n * Each box j=1,..,n is characterized by a width w_j, height h_j, and\n * depth d_j. An unlimited number of indentical three-dimensional bins,\n * having width W, height H and depth D is available. The boxes have fixed\n * orientation, i.e., they may not be rotated.\n *\n * A description of this code is found in the following papers:\n *\n *   S. Martello, D. Pisinger, D. Vigo, E. den Boef, J. Korst (2003)\n *   \"Algorithms for General and Robot-packable Variants of the \n *    Three-Dimensional Bin Packing Problem\"\n *   submitted TOMS.\n * \n *   S.Martello, D.Pisinger, D.Vigo (2000)\n *   \"The three-dimensional bin packing problem\"\n *   Operations Research, 48, 256-267\n *\n * The present code is written in ANSI-C, and has been compiled with\n * the GNU-C compiler using option \"-ansi -pedantic\" as well as the\n * HP-UX C compiler using option \"-Aa\" (ansi standard).\n *\n * This file contains the callable routine binpack3d with prototype\n *\n *   void binpack3d(int n, int W, int H, int D,\n *                  int *w, int *h, int *d, \n *                  int *x, int *y, int *z, int *bno,\n *                  int *lb, int *ub, \n *                  int nodelimit, int iterlimit, int timelimit, \n *                  int *nodeused, int *iterused, int *timeused);\n *\n * the meaning of the parameters is the following:\n *   n         Size of problem, i.e., number of boxes to be packed.\n *             This value must be smaller than MAXBOXES defined below.\n *   W,H,D     Width, height and depth of every bin.\n *   w,h,d     Integer arrays of length n, where w[j], h[j], d[j]\n *             are the dimensions of box j for j=0,..,n-1.\n *   x,y,z,bno Integer arrays of length n where the solution found\n *             is returned. For each box j=0,..,n-1, the bin number\n *             it is packed into is given by bno[j], and x[j], y[j], z[j] \n *             are the coordinates of it lower-left-backward corner.\n *   lb        Lower bound on the solution value (returned by the procedure).\n *   ub        Objective value of the solution found, i.e., number of bins\n *             used to pack the n boxes. (returned by the procedure).\n *   nodelimit maximum number of decision nodes to be explored in the\n *             main branching tree. If set to zero, the algorithm will\n *             run until an optimal solution is found (or timelimit or\n *             iterlimit is reached). Measured in thousands (see IUNIT).\n *   iterlimit maximum number of iterations in the ONEBIN algorithm\n *             which packs a single bin. If set to zero, the algorithm will\n *             run until an optimal solution is found (or timelimit or\n *             nodelimit is reached). Measured in thousands (see IUNIT).\n *   timelimit Time limit for solving the problem expressed in seconds.\n *             If set to zero, the algorithm will run until an optimal\n *             solution is found; otherwise it terminates after timelimit\n *             seconds with a heuristic solution. \n *   nodeused  returns the number of branch-and-bound nodes investigated,\n *             measured in thousands (see IUNIT).\n *   iterused  returns the number of iterations in ONEBIN algorithm,\n *             measured in thousands (see IUNIT).\n *   timeused  returns the time used in miliseconds\n *\n * (c) Copyright 1998, 2003, 2005\n *\n *   David Pisinger                        Silvano Martello, Daniele Vigo\n *   DIKU, University of Copenhagen        DEIS, University of Bologna\n *   Universitetsparken 1                  Viale Risorgimento 2\n *   Copenhagen, Denmark                   Bologna, Italy\n *\n * This code can be used free of charge for research and academic purposes \n * only. \n */              \n\n#define IUNIT        1000  /* scaling factor of nodes and iterat */ \n#define MAXBOXES      101  /* max number of boxes (plus one box) */\n#define MAXBPP    1000000  /* max numer of iterations in 1-dim bpp */\n#define MAXITER      1000  /* max iterations in heuristic onebin_robot */\n#define MAXCLOSE       16  /* max level for which try_close is applied */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <stdarg.h>\n#include <string.h>\n#include <math.h>\n#include <time.h>  \n#include <limits.h>\n\n\n/* ======================================================================\n\t\t\t\t   macros\n   ====================================================================== */\n\n#define TRUE  1           /* logical variables */\n#define FALSE 0\n\n#define WDIM  0           /* rotations of boxes */\n#define HDIM  1\n#define DDIM  2\n\n#define LEFT   0          /* relative placements */\n#define RIGHT  1\n#define UNDER  2\n#define ABOVE  3\n#define FRONT  4\n#define BEHIND 5\n#define UNDEF  6\n#define RELMAX 8\n\n#define STACKDEPTH (MAXBOXES*MAXBOXES*RELMAX)\n\n#define VOL(i)            ((i)->w * (ptype) (i)->h * (i)->d)\n#define MINIMUM(i,j)      ((i) < (j) ? (i) : (j))\n#define MAXIMUM(i,j)      ((i) > (j) ? (i) : (j))\n#define DIF(i,j)          ((int) ((j) - (i) + 1))\n#define SWAPINT(a,b)      { register int t; t=*(a);*(a)=*(b);*(b)=t; }\n#define SWAP(a,b)         { register box t; t=*(a);*(a)=*(b);*(b)=t; }\n#define SWAPI(a,b)        { register itype t; t=(a);(a)=(b);(b)=t; }\n#define SWAPP(a,b)        { register point t; t=*(a);*(a)=*(b);*(b)=t; }\n#define DF(a,b)           ((r=(a).y-(b).y) != 0 ? r : (a).x-(b).x)\n\n\n/* ======================================================================\n\t\t\t\t type declarations\n   ====================================================================== */\n\ntypedef short         boolean; /* logical variable      */\ntypedef short         ntype;   /* number of states,bins */\ntypedef short         itype;   /* can hold up to W,H,D  */\ntypedef long          stype;   /* can hold up to W*H*D  */\ntypedef long          ptype;   /* product multiplication */\n\n/* box record */\ntypedef struct irec {\n  ntype    no;           /* box number                            */\n  itype    w;            /* box width  (x-size)                   */\n  itype    h;            /* box height (y-size)                   */\n  itype    d;            /* box depth  (z-size)                   */\n  itype    x;            /* optimal x-position                    */\n  itype    y;            /* optimal y-position                    */\n  itype    z;            /* optimal z-position                    */\n  ntype    bno;          /* bin number                            */\n  boolean  k;            /* is the box chosen?                    */\n  stype    vol;          /* volume of box                         */\n  struct irec *ref;      /* reference to original box (if necessary) */\n} box; \n\n/* all problem information */\ntypedef struct {\n  itype    W;            /* x-size of bin                         */\n  itype    H;            /* y-size of bin                         */\n  itype    D;            /* z-size of bin                         */\n  stype    BVOL;         /* volume of a bin                       */\n  ntype    n;            /* number of boxes                       */\n  box      *fbox;        /* first box in problem                  */\n  box      *lbox;        /* last box in problem                   */\n  box      *fsol;        /* first box in current solution         */\n  box      *lsol;        /* last box in current solution          */\n  box      *fopt;        /* first box in optimal solution         */\n  box      *lopt;        /* last box in optimal solution          */\n  boolean  *closed;      /* for each bin indicator whether closed */\n  box      *fclosed;     /* first box in closed bins              */\n  box      *lclosed;     /* last box in closed bins               */\n  ntype    noc;          /* number of closed bins                 */\n  itype    mindim;       /* currently smallest box length         */\n  itype    maxdim;       /* currently largest box length          */\n  stype    maxfill;      /* the best filling found                */\n  int      mcut;         /* how many siblings at each node in b&b */\n\n  /* different bounds */\n  ntype    bound0;       /* Bound L_0 at root node                */\n  ntype    bound1;       /* Bound L_1 at root node                */\n  ntype    bound2;       /* Bound L_2 at root node                */\n  ntype    lb;           /* best of the above                     */\n  ntype    z;            /* currently best solution               */\n\n  /* controle of 3d filler */\n  int      maxiter;      /* max iterations in onebin_robot        */\n  int      miss;         /* number boxes not packed in onebin_robot */\n\n  /* debugging and controle information */\n  int      nodes;        /* nodes in branch-and-bound             */\n  int      iterat;       /* iterations in onebin_decision         */\n  int      subnodes;     /* nodes in branch-and-bound             */\n  int      subiterat;    /* iterations in onebin_decision         */\n  int      exfill;       /* number of calls to onebin_decision    */\n  int      iter3d;       /* iterations in onebin_robot or general */\n  int      zlayer;       /* heuristic solution layer              */\n  int      zmcut;        /* heuristic solution mcut               */\n  double   exacttopo;    /* number of topological sorts           */\n  double   exacttopn;    /* number of topological sorts           */\n  int      exactcall;    /* number of calls to exact              */\n  int      exactn;       /* largest problem for exact             */\n  double   genertime;    /* time used in onebin_general           */\n  double   robottime;    /* time used in onebin_robot             */\n  double   time;         /* computing time                        */\n  double   lhtime;       /* layer heuristic computing time        */\n  double   mhtime;       /* mcut heuristic computing time         */\n  int      didpush;      /* did the lower bound push up bound     */\n  int      maxclose;     /* max number of closed bins at any time */\n  int      nodelimit;    /* maximum number of nodes in main tree  */\n  int      iterlimit;    /* maximum number of iterations in ONEBIN*/\n  int      timelimit;    /* maximum amount of time to be used     */\n} allinfo;\n\n/* structure for greedy algorithm */\ntypedef struct {\n  int      lno;          /* layer number                          */\n  int      d;            /* depth of layer                        */\n  int      bno;          /* bin no assigned to layer              */\n  int      z;            /* z level of layer within bin           */\n  int      b;            /* temporary bin number                  */\n} heurpair;\n\n/* structure for extreme points in a single bin */\ntypedef struct {\n  itype    x;            /* x-coordinate                          */\n  itype    y;            /* y-coordinate                          */\n  itype    z;            /* z-coordinate                          */\n} point;\n\n/* structure for a domain pair in constraint programming */\ntypedef struct {\n  int i;                 /* index of box i                        */\n  int j;                 /* index of box j                        */\n  int relation;          /* relation between the two boxes        */\n  boolean domain;        /* domain of the two boxes               */\n} domainpair;\n  \n\n/* set of domains */\ntypedef char domset[RELMAX];\ntypedef domset domline[MAXBOXES];\n\n/* pointer to comparison function */\ntypedef int (*funcptr) (const void *, const void *); \n\n\n/* ======================================================================\n\t\t\t\t  global variables\n   ====================================================================== */\n\n/* boolean variable to indicate time-out situation */\nboolean stopped; \n\n/* counter used to ensure that 1D BPP at most performs MAXBPP iterations */\nint bpiterat;\n\n/* boolean variables to indicate when 1D packing algorithm should terminate */\nboolean feasible, terminate;\n\n/* stack of domain pairs */\ndomainpair domstack[STACKDEPTH];\ndomainpair *dompos, *domend; \n\n/* domain of each box */\ndomline domain[MAXBOXES];\n\n/* current relation between two boxes */\nchar relation[MAXBOXES][MAXBOXES];\n\n/* debug variable to see level in recursive packing algorithm */\nint bblevel;\n\n\n/* =======================================================================\n\t\t\t\t  error\n   ======================================================================= */\n\nvoid error(char *str, ...)\n{\n  va_list args;\n\n  va_start(args, str);\n  vprintf(str, args); printf(\"\\n\");\n  va_end(args);\n  printf(\"IRREGULAR PROGRAM TERMINATION\\n\");\n  exit(-1);\n}\n\n\n/* **********************************************************************\n   **********************************************************************\n\t\t\t     Timing routines \n   **********************************************************************\n   ********************************************************************** */\n\n/* This timing routine is based on the ANSI-C procedure \"clock\", which\n * has a resolution of 1000000 ticks per second. This however implies\n * that we pass the limit of a long integer after only 4295 seconds.\n * The following routine attempts to correct such situations by adding\n * the constant ULONG_MAX to the counter whenever wraparound can be\n * detected. But the user is advised to use a timing routine like \"times\"\n * (which however is not ANSI-C standard) for measuring longer time\n * periods.\n */\n\nvoid timer(double *time)\n{\n  static double tstart, tend, tprev;\n\n  if (time == NULL) {\n    clock(); /* one extra call to initialize clock */\n    tstart = tprev = clock();\n  } else {\n    tend = clock(); \n    if (tend < tprev) tstart -= ULONG_MAX; /* wraparound occured */\n    tprev = tend;\n    *time = (tend-tstart) / CLOCKS_PER_SEC; /* convert to seconds */\n  }\n}\n\n/* test for time limit */\nvoid check_timelimit(long max)\n{\n  double t;\n  if (max == 0) return;\n  timer(&t); \n  if (t >= max) { \n    if (!stopped) printf(\"TIMELIMIT\\n\"); \n    stopped = TRUE; \n  }\n}\n\n/* test for node limit */\nvoid check_nodelimit(long nodes, long max)\n{\n  if (max == 0) return;\n  if (nodes >= max) { \n    if (!stopped) printf(\"NODELIMIT\\n\"); \n    stopped = TRUE; \n  }\n}\n\n/* test for iteration limit */\nvoid check_iterlimit(long iterations, long max)\n{\n  if (max == 0) return;\n  if (iterations >= max) { \n    if (!stopped) printf(\"ITERLIMIT\\n\"); \n    stopped = TRUE; \n  } \n}\n\n\n\n/* **********************************************************************\n   **********************************************************************\n\t\t\t     Small procedures\n   **********************************************************************\n   ********************************************************************** */\n\n/* ======================================================================\n\t\t\t    simple comparisions\n   ====================================================================== */\n\n/* Comparisons used as argument to qsort. */\n\nint dcomp(box *a, box *b) \n{ int r; r = b->d - a->d; if (r != 0) return r; else return b->no - a->no; }\nint hcomp(box *a, box *b) \n{ int r; r = b->h - a->h; if (r != 0) return r; else return b->no - a->no; }\nint vcomp(box *a, box *b) /* volume decr. */\n{ int r; r = b->vol-a->vol; if (r != 0) return r; else return b->no - a->no; }\nint xcomp(heurpair *a, heurpair *b) /* depth decr. */\n{ int r; r = b->d - a->d; if (r != 0) return r; else return b->lno - a->lno; }\nint lcomp(heurpair *a, heurpair *b) /* layer number decr. */\n{ int r; r = a->lno-b->lno; if (r != 0) return r; else return b->d - a->d; }\n\n\n/* ======================================================================\n\t\t\t\t  palloc\n   ====================================================================== */\n\n/* Memory allocation and freeing, with implicit check */\n\nvoid *palloc(long sz, long no)\n{\n  long size;\n  void *p;\n\n  size = sz * no;\n  if (size == 0) size = 1;\n  p = (void *) malloc(size);\n  if (p == NULL) error(\"no memory size %ld\", size);\n  return p;\n}\n\nvoid pfree(void *p)\n{\n  if (p == NULL) error(\"freeing null\");\n  free(p);\n}\n\n\n/* ======================================================================\n\t\t\t     checksol\n   ====================================================================== */\n\n/* Check correctnes of solution, i.e., no boxes overlap, no duplicated boxes.\n */\n\nvoid checksol(allinfo *a, box *f, box *l)\n{\n  box *i, *j, *m;\n  for (i = f, m = l+1; i != m; i++) { \n    if (!i->k) continue;  /* box currently not chosen */\n    for (j = f; j != m; j++) {\n      if (i == j) continue;\n      if (i->no == j->no) error(\"duplicated box %d\\n\", i->no); \n      if (!j->k) continue;\n      if (i->bno != j->bno) continue;\n      if ((i->x + i->w > j->x) && (j->x + j->w > i->x) &&\n\t  (i->y + i->h > j->y) && (j->y + j->h > i->y) &&\n\t  (i->z + i->d > j->z) && (j->z + j->d > i->z)) {\n\terror(\"overlap box %d,%d: [%d,%d,%d] [%d,%d,%d]\",\n\t      i->no, j->no, i->w, i->h, i->d, j->w, j->h, j->d);\n      }\n    }\n  }\n}\n\n/* ======================================================================\n\t\t\t       savesol\n   ====================================================================== */\n\n/* save an updated solution, checking its validity */\n\nvoid savesol(allinfo *a, box *f, box *l, ntype z)\n{\n  box *i, *k, *m;\n\n  /* first check validity */\n  if (z >= a->z) error(\"not improved\");\n  for (i = f, m = l+1; i != m; i++) {\n    if ((1 <= i->bno) && (i->bno <= z)) continue; \n    error(\"illegal bin %d, box %d\", i->bno, i->no); \n  }\n\n  /* now do the saving */\n  a->z = z;\n  for (i = f, k = a->fopt, m = l+1; i != m; i++, k++) *k = *i;\n  for (i = a->fclosed, m = a->lclosed+1; i != m; i++, k++) *k = *i;\n  for (i = a->fopt, m = a->lopt+1; i != m; i++) i->k = TRUE;\n  if (DIF(a->fopt,k-1) != a->n) error(\"not correct amount of boxes\");\n  checksol(a, a->fopt, a->lopt);\n}\n\n\n/* ======================================================================\n\t\t\t       isortincr\n   ====================================================================== */\n\n/* A specialized routine for sorting integers in increasing order. */\n/* qsort could be used equally well, but this routine is faster. */\n\nvoid isortincr(int *f, int *l)\n{\n  register int mi;\n  register int *i, *j, *m;\n  register int d;\n\n  d = l - f + 1;\n  if (d < 1) error(\"negative interval in isortincr\");\n  if (d == 1) return;\n  m = f + d / 2; if (*f > *m) SWAPINT(f, m);\n  if (d > 2) { if (*m > *l) { SWAPINT(m, l); if (*f > *m) SWAPINT(f, m); } }\n  if (d <= 3) return;\n  mi = *m; i = f; j = l;\n  for (;;) {\n    do i++; while (*i < mi);\n    do j--; while (*j > mi);\n    if (i > j) break; else SWAPINT(i, j);\n  }\n  isortincr(f, i-1); isortincr(i, l);\n}\n\n\n/* ======================================================================\n\t\t\t       psortdecr\n   ====================================================================== */\n\n/* A specialized routine for sorting extreme points according to decreasing */\n/* y-coordinate (decreasing x-coordinate in case of ties) */\n\nvoid psortdecr(point *f, point *l)\n{\n  register point mi;\n  register point *i, *j, *m;\n  register int d, r;\n\n  d = l - f + 1; \n  if (d <= 1) return;\n  m = f + d / 2; if (DF(*f,*m)<0) SWAPP(f,m); \n  if (d == 2) return;\n  if (DF(*m,*l)<0) { SWAPP(m,l); if (DF(*f,*m)<0) SWAPP(f,m); }\n  if (d <= 3) return;\n  mi = *m; i = f; j = l;\n  for (;;) {\n    do i++; while (DF(*i,mi) > 0);\n    do j--; while (DF(*j,mi) < 0);\n    if (i > j) break; else SWAPP(i, j);\n  }\n  psortdecr(f, i-1); psortdecr(i, l);\n}\n\n\n/* **********************************************************************\n   **********************************************************************\n\t\t\t      Lower Bounds\n   **********************************************************************\n   ********************************************************************** */\n\n/* ======================================================================\n\t\t\t      bound_zero\n   ====================================================================== */\n\n/* The continuous bound L_0 */\n\nint bound_zero(allinfo *a, box *f, box *l)\n{\n  box *i, *m;\n  stype vsum, lb;\n\n  vsum = 0; \n  for (i = f, m = l+1; i != m; i++) vsum += i->vol;\n  lb = (stype) ceil(vsum / (double) a->BVOL);\n  return lb;\n}\n\n\n/* ======================================================================\n                               rotate_solution\n   ====================================================================== */\n\n/* rotates the solution. After 3 rotations we return to original problem */\n\nvoid rotate_solution(allinfo *a, box *f, box *l)\n{\n  register box *i, *m;\n  register itype w, x;\n\n  for (i = f, m = l+1; i != m; i++) {\n    w = i->w; i->w = i->h; i->h = i->d; i->d = w;\n    x = i->x; i->x = i->y; i->y = i->z; i->z = x;\n  }\n}\n\n\n/* ======================================================================\n\t\t\t       rotate_problem\n   ====================================================================== */\n\n/* rotates the dimensions by one step */\n\nvoid rotate_problem(allinfo *a, box *f, box *l)\n{\n  register box *i, *m;\n  register itype w, x;\n\n  for (i = f, m = l+1; i != m; i++) {\n    w = i->w; i->w = i->h; i->h = i->d; i->d = w;\n    x = i->x; i->x = i->y; i->y = i->z; i->z = x;\n  }\n  w = a->W; a->W = a->H; a->H = a->D; a->D = w;\n}\n\n\n/* ======================================================================\n\t\t\t       choose_boxes\n   ====================================================================== */\n\n/* returns a set of boxes with w > W2 and d > D2. This set is used in */\n/* bound_one */\n\nvoid choose_boxes(allinfo *a, box *f, box *l, int W2, int D2, \n\t\t  box *fbox , box **lbox)\n{\n  box *i, *k, *m;\n\n  for (i = f, m = l+1, k = fbox; i != m; i++) {\n    if ((i->w > W2) && (i->d > D2)) { *k = *i; k++; }\n  }\n  *lbox = k-1;\n}\n\n\n/* ======================================================================\n\t\t\t       find_plist\n   ====================================================================== */\n\n/* returns a zero-terimanted list of distinct dimensions */\n\nvoid find_plist(box *fbox, box *lbox, itype M, int dim, int *pl)\n{\n  register box *i, *m;\n  register int *k, *j, *l;\n\n  i = fbox; m = lbox+1; k = pl;\n  switch (dim) {\n    case WDIM: for (; i != m; i++) {\n\t\t if (i->w <= M) { *k = i->w; k++; } \n\t       } break;\n    case HDIM: for (; i != m; i++) {\n\t\t if (i->h <= M) { *k = i->h; k++; } \n\t       } break;\n    case DDIM: for (; i != m; i++) {\n\t\t if (i->d <= M) { *k = i->d; k++; } \n\t       } break;\n  }\n  if (k == pl) { *k = 0; return; }\n  isortincr(pl, k-1);  /* sort the dimensions */\n  for (j = pl+1, l = pl; j != k; j++) { /* remove duplicates */\n    if (*j != *l) { l++; *l = *j; } \n  }\n  l++; *l = 0;\n}\n\n\n/* ======================================================================\n\t\t\t       bound_one\n   ====================================================================== */\n\n/* Derive bound L_1 for a fixed dimension */\n\nint bound_one_x(allinfo *a, box *f, box *l)\n{\n  register box *i, *m;\n  register itype H, H2, h;\n  register int p, j1, j2, j3, j2h, j2hp, j3h;\n  int *pp, lb, lb_one, alpha, beta;\n  box fbox[MAXBOXES], *lbox;\n  int plist[MAXBOXES];\n\n  if (l == f-1) return 0;\n  lb = 1; H = a->H; H2 = H/2;\n  choose_boxes(a, f, l, a->W/2, a->D/2, fbox, &lbox);\n  if (lbox == fbox-1) { /* empty */ return lb; }\n\n  find_plist(fbox, lbox, H2, HDIM, plist);\n  for (pp = plist; *pp != 0; pp++) {\n    p = *pp; j1 = j2 = j3 = j2h = j2hp = j3h = 0;\n    for (i = fbox, m = lbox+1; i != m; i++) {\n      h = i->h; \n      if (h > H-p) j1++;\n      if ((H-p >= h) && (h > H2)) { j2++; j2h += h; j2hp += (H-h)/p; }\n      if ((H2 >= h) && (h >= p)) { j3++; j3h += h; }\n    }\n    alpha = (int) ceil((j3h - (j2 * H - j2h)) / (double) H);\n    beta  = (int) ceil((j3 - j2hp) / (double) (H/p));\n    if (alpha < 0) alpha = 0;\n    if (beta  < 0) beta  = 0;\n    lb_one = j1 + j2 + MAXIMUM(alpha, beta);\n    if (lb_one > lb) lb = lb_one;\n  }\n  return lb;\n}\n\n\n/* Derive bound L_1 as the best of all L_1 bounds for three rotations */\n\nint bound_one(allinfo *a, box *f, box *l)\n{\n  int i, lb, lbx;\n\n  lb = 0;\n  for (i = WDIM; i <= DDIM; i++) {\n    lbx = bound_one_x(a, f, l);\n    if (lbx > lb) lb = lbx; \n    rotate_problem(a, f, l);\n  } \n  return lb;\n}\n\n\n/* ======================================================================\n\t\t\t       bound_two\n   ====================================================================== */\n\n/* Derive bound L_2 for a fixed dimension */\n\nint bound_two_x(allinfo *a, box *f, box *l)\n{\n  register box *i, *m;\n  register itype W, H, D, w, h, d, W2, D2;\n  register int p, q, k1h, k23v;\n  int hlb1, lb, lb1, lbx, fract;\n  int plist[MAXBOXES], qlist[MAXBOXES];\n  int *qq, *pp;\n  double WD, BVOL;\n\n  /* derive bound_one */\n  lb = lb1 = bound_one_x(a, f, l);\n  W = a->W; H = a->H; D = a->D; hlb1 = H * lb1; \n  W2 = W/2; D2 = D/2; WD = W*(double)D; BVOL = a->BVOL;\n\n  /* run through all values of p, q */\n  find_plist(f, l, W2, WDIM, plist);\n  find_plist(f, l, D2, DDIM, qlist);\n  for (pp = plist; *pp != 0; pp++) {\n    p = *pp;\n    for (qq = qlist; *qq != 0; qq++) {\n      q = *qq;\n      k1h = k23v = 0;\n      for (i = f, m = l+1; i != m; i++) {\n\tw = i->w; h = i->h; d = i->d;\n\tif ((w > W - p) && (d > D - q)) { k1h += h; continue; }\n\tif ((w >= p) && (d >= q)) { k23v += i->vol; }\n      }\n      fract = (int) ceil((k23v - (hlb1 - k1h)*WD) / BVOL);\n      if (fract < 0) fract = 0;\n      lbx = lb1 + fract;\n      if (lbx > lb) lb = lbx;\n    }\n  }\n  return lb;\n}\n\n\n/* Derive bound L_2 as the best of all L_2 bounds for three rotations */\n\nint bound_two(allinfo *a, box *f, box *l)\n{\n  int i, lb, lbx;\n\n  lb = 0;\n  for (i = WDIM; i <= DDIM; i++) {\n    lbx = bound_two_x(a, f, l);\n    if (lbx > lb) lb = lbx;\n    rotate_problem(a, f, l);\n  } \n  return lb;\n}\n\n\n/* **********************************************************************\n   **********************************************************************\n\t\t\t    heuristic filling\n   **********************************************************************\n   ********************************************************************** */\n\n/* ======================================================================\n\t\t\t      onelayer\n   ====================================================================== */\n\n/* Fill a layer of depth f->d by arranging the boxes in a number of */\n/* vertical shelfs. The boxes $i$ packed are assigned coordinates */\n/* (i->x, i->y) and the field i->k is set to the argument d (layer no). */\n\nvoid onelayer(allinfo *a, box *f, box *m, box *l, int d)\n{\n  int s, t;\n  itype r; /* remaining width */\n  itype width[MAXBOXES], height[MAXBOXES], x[MAXBOXES];\n  box *i;\n  \n  qsort(f, DIF(f,m), sizeof(box), (funcptr) hcomp);\n  r = a->W;\n  x[0] = 0; width[0] = 0; height[0] = 0;\n  for (s = 1, i = f; i != l+1; s++) {\n    x[s] = x[s-1] + width[s-1];\n    height[s] = 0;\n    width[s] = i->w; if (width[s] > r) width[s] = r;\n    r -= width[s];\n    for ( ; i != l+1; i++) {\n      for (t = s; t != 0; t--) {\n\tif (i->w <= width[t]) {\n\t  if (height[t] + i->h <= a->H) {\n\t    i->y = height[t]; i->x = x[t]; i->k = d;\n\t    height[t] += i->h; break;\n\t  }\n\t}\n      }\n      if ((t == 0) && (r > 0)) break; /* new strip */\n    }\n  }\n}\n\n\n/* ======================================================================\n\t\t\t\t  countarea\n   ====================================================================== */\n\n/* Select a subset of the boxes such that the selected boxes have a total */\n/* area of two times the face of a bin (the parameter: barea) */\n\nbox *countarea(box *f, box *l, stype barea)\n{\n  box *i;\n  stype area, d;\n\n  for (area = 0, i = f; i != l+1; i++) {\n    d = i->h * (ptype) i->w;\n    area += d;\n    if (area > 2*barea) return i-1;\n  }\n  return l;\n}\n\n\n/* ======================================================================\n\t\t\t\t  remboxes\n   ====================================================================== */\n\n/* Remove the boxes which were chosen for a layer, i.e., where i->k != 0. */\n/* The depth of the layer is set equal to the deepest box chosen. */\n\nbox *remboxes(box *f, box *l, itype *depth)\n{\n  box *i, *j;\n  itype d;\n\n  for (i = f, j = l, d = 0; i != j+1; ) {\n    if (i->k) { if (i->d > d) d = i->d; i++; } else { SWAP(i,j); j--; }\n  }\n  *depth = d;\n  return i;\n}\n\n\n/* ======================================================================\n\t\t\t\t  assignboxes\n   ====================================================================== */\n\n/* Assign z-coordinates to the boxes, once they layers have been combined */\n/* to individual bins by solving a 1-dimensional Bin-packing Problem. */\n\nvoid assignboxes(heurpair *t, heurpair *u, ntype maxbin, box *f, box *l)\n{\n  box *i, *m;\n  heurpair *h;\n  itype b, z;\n\n  /* derive z-coordinates for each layer */\n  for (b = 1; b <= maxbin; b++) {\n    z = 0;\n    for (h = t; h <= u; h++) if (h->bno == b) { h->z = z; z += h->d; }\n  }\n\n  for (i = f, m = l+1; i != m; i++) {\n    h = t + i->k - 1;\n    i->z = h->z; i->bno = h->bno;\n  }\n}\n\n\n/* ======================================================================\n\t\t\t\t onedim_binpack\n   ====================================================================== */\n\n/* One-dimensional bin-packing algorithm. In each iteration, the next */\n/* box is assigned to every open bin as well as to a new bin. The  */\n/* algorithm terminates when MAXBPP iterations have been performed, */\n/* returning the heuristic solution found. */\n\nvoid onedim_binpack(heurpair *i, heurpair *f, heurpair *l, \n\t\t    int *b, int bno, itype *z)\n{\n  int j, *bc;\n  heurpair *k;\n\n  bpiterat++; if (bpiterat > MAXBPP) return;\n  if (bno >= *z) return; /* no hope of improvement */\n\n  if (i > l) {\n    *z = bno; \n    for (k = f; k <= l; k++) k->bno = k->b;\n  } else {\n    for (j = 0; j < bno; j++) {\n      bc = b + j;\n      if (i->d <= *bc) {\n\t*bc -= i->d; i->b = j+1;\n\tonedim_binpack(i+1, f, l, b, bno, z);\n\t*bc += i->d;\n      }\n    }\n    bc = b + bno;\n    *bc -= i->d; i->b = bno+1;\n    onedim_binpack(i+1, f, l, b, bno+1, z);\n    *bc += i->d;\n  }\n}\n\n\n/* ======================================================================\n\t\t\t      dfirst_heuristic\n   ====================================================================== */\n\n/* Heuristic algorithm for the 3D BPP. A number of layers are constructed */\n/* using the shelf approach to pack every layer. Then the individual layers */\n/* are combined to bins by solving a 1D BPP defined in the layer depths. */\n\nvoid dfirst_heuristic(allinfo *a)\n{\n  box *j, *f, *l, *m;\n  int i, n, h, b[MAXBOXES];\n  heurpair t[MAXBOXES];\n  itype d, z;\n\n  /* initialize boxes */\n  for (j = a->fbox, m = a->lbox+1; j != m; j++) {\n    j->bno = j->x = j->y = j->z = 0; j->k = FALSE;\n  }\n\n  /* fill layer one by one */\n  for (f = a->fbox, l = a->lbox, h = 0; ; h++) {\n    n = DIF(f,l); if (n == 0) break;\n    qsort(f, n, sizeof(box), (funcptr) dcomp);\n    m = countarea(f, l, a->W * (ptype) a->H);\n    onelayer(a, f, m, l, h+1);\n    f = remboxes(f, l, &d);\n    t[h].d = d; t[h].bno = h+1;  /* initially put layers into separate bins */\n    t[h].z = 0; t[h].lno = h+1;  /* this ensures fes. solution if terminate */\n  }\n\n  /* split into bins by solving 1-dim binpacking */\n  for (i = 0; i < h; i++) b[i] = a->D;  /* all bins are empty */\n  qsort(t, h, sizeof(heurpair), (funcptr) xcomp);\n  z = h+1; bpiterat = 0;\n  onedim_binpack(t, t, t+h-1, b, 0, &z); \n  qsort(t, h, sizeof(heurpair), (funcptr) lcomp); /* order according to lno */\n\n  /* now assign bin number to each boxes */\n  assignboxes(t, t+h-1, z, a->fbox, a->lbox);\n  if (z < a->zlayer) a->zlayer = z;\n  if (a->zlayer < a->z) savesol(a, a->fbox, a->lbox, a->zlayer);\n}\n\n\n/* ======================================================================\n\t\t\t\tdfirst3_heuristic\n   ====================================================================== */\n\n/* Call the heuristic dfirst_heuristic, for three different rotations */\n/* of the problem */\n\nvoid dfirst3_heuristic(allinfo *a)\n{\n  int i;\n  double t1, t2;\n \n  timer(&t1); \n  a->zlayer = a->n;  /* very bad incumbent solution */\n  for (i = WDIM; i <= DDIM; i++) {\n    dfirst_heuristic(a);\n    rotate_solution(a, a->fopt, a->lopt);\n    rotate_problem(a, a->fbox, a->lbox);\n  }\n  timer(&t2); \n  a->lhtime = t2 - t1;\n}\n\n\n/* **********************************************************************\n   **********************************************************************\n                    fill one 3D bin using GENERAL packing\n   **********************************************************************\n   ********************************************************************** */\n\n\n/* ======================================================================\n  \t\t\t         modifyandpush\n   ====================================================================== */\n\n/* Push the relation \"rel\" between box i and box j to a stack. If \"dom\"\n * is true, the relation is removed from the domain. If \"dom\" is false,\n * the relation \"rel\" is imposed between boxes \"i\" and \"j\".\n */\n\nvoid modifyandpush(int i, int j, int rel, boolean dom)\n{\n  dompos->i = i;\n  dompos->j = j;\n  dompos->domain = dom;\n  if (dom) { \n    dompos->relation = rel;\n    domain[i][j][rel] = FALSE; \n  } else {\n    dompos->relation = relation[i][j];\n    relation[i][j] = rel; \n  }\n  dompos++; if (dompos == domend) error(\"stack filled\\n\");\n}\n\n\n/* ======================================================================\n  \t\t\t         popdomains\n   ====================================================================== */\n\n/* Pop all relations between boxes from the stack. The stack is emptied\n * downto the depth given by \"pos\".\n */\n\nvoid popdomains(domainpair *pos)\n{\n  for (; dompos != pos; ) {\n    dompos--;\n    if (dompos->domain) {\n      domain[dompos->i][dompos->j][dompos->relation] = TRUE;\n    } else {\n      relation[dompos->i][dompos->j] = dompos->relation;\n    }\n  }\n}\n\n\n/* ======================================================================\n  \t\t\t         findcoordinates\n   ====================================================================== */\n\n/* Find coordinates of boxes according to the currently pending relations.\n * In principle this can be done by topologically sorting the boxes according\n * to e.g. the left-right relations, and then assigning coordinates from\n * the left-most box to the right-most box.\n *   The following implementation is a simplified version, which runs through\n * all pairs of boxes, and checks whether they satisfy the relation, otherwise\n * moving one of the boxes right, up or behind, according to the given\n * relation. The process is repeated until no pairs of boxes violate a\n * relation. Computational experiments have shown that the present approach\n * for the considered instances is faster than a topological sorting followed\n * by a critical path calculation.\n *   If a box during the process gets moved outsides of the bin, then\n * the algorithm terminates with FALSE. Otherwise TRUE is returned, saying\n * that a feasible packing exists which respects the current relations.\n */\n\nboolean findcoordinates(allinfo *a, int n, box *f)\n{\n  register box *g, *h;\n  register int sum;\n  int i, j, k, W, H, D;\n  boolean changed;\n  char *dom, *relij;\n  domset *domij;\n\n  /* check if feasible, i.e., at least one choice for each relation */\n  W = a->W; H = a->H; D = a->D; \n  for (i = 0; i < n; i++) {\n    j = i+1;\n    relij = &(relation[i][j]); \n    domij = &(domain[i][j]); \n    for ( ; j < n; j++, relij++, domij++) {\n      if (*relij != UNDEF) continue;\n      dom = *domij; if (*dom) continue; \n      dom++;        if (*dom) continue;\n      dom++;        if (*dom) continue;\n      dom++;        if (*dom) continue;\n      dom++;        if (*dom) continue;\n      dom++;        if (*dom) continue;\n      return FALSE;\n    }\n  }\n\n  /* initialize coordinates */\n  for (i = 0; i < n; i++) { g = f+i; g->x = g->y = g->z = 0; } \n\n  /* now determine the coordinates */\n  a->exacttopo++;\n  for (k = 0; k < n; k++) {\n    a->exacttopn++;\n    changed = FALSE; \n    for (i = 0; i < n; i++) {\n      g = f+i; j = i+1; relij = &(relation[i][j]);\n      for ( ; j < n; j++, relij++) {\n        h = f+j;\n        switch (*relij) {\n          case UNDEF :\n            /* do nothing */\n            break;\n          case LEFT  : \n            sum = g->x + g->w;\n            if (h->x < sum) {\n              h->x = sum; changed = TRUE; if (sum + h->w > W) return FALSE;\n            }\n            break;\n          case RIGHT : \n            sum = h->x + h->w;\n            if (g->x < sum) {\n              g->x = sum; changed = TRUE; if (sum + g->w > W) return FALSE;\n            }\n            break;\n          case UNDER : \n            sum = g->y + g->h;\n            if (h->y < sum) {\n              h->y = sum; changed = TRUE; if (sum + h->h > H) return FALSE;\n            }\n            break;\n          case ABOVE : \n            sum = h->y + h->h;\n            if (g->y < sum) {\n              g->y = sum; changed = TRUE; if (sum + g->h > H) return FALSE;\n            }\n            break;\n          case FRONT : \n            sum = g->z + g->d;\n            if (h->z < sum) {\n              h->z = sum; changed = TRUE; if (sum + h->d > D) return FALSE;\n            }\n            break;\n          case BEHIND: \n            sum = h->z + h->d;\n            if (g->z < sum) {\n              g->z = sum; changed = TRUE; if (sum + g->d > D) return FALSE;\n            }\n            break;\n        }\n      }\n    }\n    if (!changed) { return TRUE; }\n  }\n  /* there must be a loop in the graph */\n  return FALSE;\n}\n\n\n/* ======================================================================\n  \t\t\t         checkdomain\n   ====================================================================== */\n\n/* Temporarily impose the relation \"value\" between boxes \"i\" and \"j\",\n * and check whether a feasible assignment of coordinates exists which\n * respects all currently imposed relations. \n *   If the relation cannot be satisfied, it is removed from the domain\n * and pushed to a stack, so that it can be restored upon backtracking.\n */\n\nvoid checkdomain(allinfo *a, int i, int j, int n, box *f, int value)\n{\n  if (domain[i][j][value] == FALSE) return; /* not allowed in any case */\n  relation[i][j] = value;\n  if (findcoordinates(a, n, f) == FALSE) {\n    modifyandpush(i, j, value, TRUE);\n  } \n}\n\n\n/* ======================================================================\n  \t\t\t         reducedomain\n   ====================================================================== */\n\n/* Constraint propagation algorithm. For each relation in the domain of\n * boxes \"i\" and \"j\", check if the relation has the posibility of being\n * satisfied. If some of the relations cannot be satisfied any more, they\n * are removed from the domain (and pushed to a stack, so that they can\n * be restored when the master search algorithm backtracks). If only one\n * relation remains in the domain, the relation is imposed at this node\n * and all descendant nodes.\n */\n\nboolean reducedomain(allinfo *a, int n, box *f)\n{\n  register int i, j, k, l, m;\n\n  m = 0;\n  for (i = 0; i < n-1; i++) {\n    for (j = i+1; j < n-1; j++) {\n      if (relation[i][j] == UNDEF) {\n        checkdomain(a, i, j, n, f, LEFT);\n        checkdomain(a, i, j, n, f, RIGHT);\n        checkdomain(a, i, j, n, f, UNDER);\n        checkdomain(a, i, j, n, f, ABOVE);\n        checkdomain(a, i, j, n, f, FRONT);\n        checkdomain(a, i, j, n, f, BEHIND);\n        relation[i][j] = UNDEF;\n        for (k = LEFT, l = 0; k < UNDEF; k++) {\n          if (domain[i][j][k]) { l++; m = k; }\n        }\n        if (l == 0) return FALSE;\n        if (l == 1) { modifyandpush(i, j, m, FALSE); }\n      }\n    } \n  }\n  return TRUE;\n}\n\n\n/* ======================================================================\n  \t\t\t         recpack\n   ====================================================================== */\n\n/* Recursive algorithm based on constraint programming used for assigning\n * relative positions to each pair of boxes. Each pair of boxes initially \n * has an associated relation with domain LEFT, RIGHT, UNDER, ABOVE, FRONT, \n * BEHIND. In each iteration of the algorithm a pair of boxes \"i\" and \"j\"\n * is assigned the relation \"rel\". Constraint propagation is then used to \n * decrease the domains of remaining relations. \n *   If it is not possible to assign coordinates to the boxes such that the \n * currently imposed relations between pairs of boxes are respected, we \n * backtrack.\n *   If each pair of boxes has been assigned a relation, and it is possible\n * to assign coordinates to the boxes such that the currently imposed \n * relations between pairs of boxes are respected, we save the solution \n * and return.\n *   Otherwise, constraint propagation is used to decrease the domains\n * of relations corresponding to each pairs of boxes. If a domain only\n * contains a single relation, the relation is fixed.\n *   The recursive step selects the next pair of boxes following \"i\" and \"j\"\n * and repeatedly assigns each relation from the domain to the relation \n * variable.\n */\n\nvoid recpack(allinfo *a, int i, int j, int n, box *f, int rel)\n{\n  int i1, j1;\n  domainpair *pos;\n  boolean feas;\n\n  if (stopped) return;\n  a->iter3d++;\n  if ((a->iter3d == a->maxiter) && (a->maxiter != 0)) terminate = TRUE;\n  a->subiterat++; \n  if (a->subiterat == IUNIT) { \n    a->subiterat = 0;\n    a->iterat++; check_iterlimit(a->iterat, a->iterlimit);\n    check_timelimit(a->timelimit);\n  }\n  if (terminate) return;\n\n  relation[i][j] = rel;\n\n  for (i1 = 0, j1 = 0; i1 != i && j1 != j; ) {\n    i1++; if (i1 >= j1) { i1 = 0; j1++; }\n    if (relation[i1][j1] == UNDEF) error(\"relation error %d %d\\n\", i1, j1);\n  }  \n\n  feas = findcoordinates(a, n, f); \n  if (!feas) return;\n\n  if ((i == n-2) && (j == n-1)) { \n    feasible = TRUE;\n    terminate = TRUE;\n    memcpy(a->fsol, f, sizeof(box) * n); \n    return;\n  }\n\n  pos = dompos;\n  feas = reducedomain(a, n, f);\n  if (feas) {\n    i++; if (i >= j) { i = 0; j++; }\n    bblevel++;\n    rel = relation[i][j];\n    if (domain[i][j][LEFT ]) recpack(a, i, j, n, f, LEFT);\n    if (domain[i][j][RIGHT]) recpack(a, i, j, n, f, RIGHT);\n    if (domain[i][j][UNDER]) recpack(a, i, j, n, f, UNDER);\n    if (domain[i][j][ABOVE]) recpack(a, i, j, n, f, ABOVE);\n    if (domain[i][j][FRONT]) recpack(a, i, j, n, f, FRONT);\n    if (domain[i][j][BEHIND])recpack(a, i, j, n, f, BEHIND);\n    relation[i][j] = rel;\n    bblevel--;\n  }\n  popdomains(pos);\n}\n\n\n/* ======================================================================\n\t\t\t\t general_pack\n   ====================================================================== */\n\n/* General packing procedure, which tests whether boxes f..l can be packed\n * into a single bin. The algorithm is based on constraint programming, where\n * each pair of boxes initially has an associated relation with domain LEFT, \n * RIGHT, UNDER, ABOVE, FRONT, BEHIND. Then the recursive algorithm \"recpack\" \n * is called, which repeatedly tries to assign the relation a value, using\n * constraint propagation to decrease the domains of remaining boxes.\n */\n\nboolean general_pack(allinfo *a, box *f, box *l)\n{\n  register int i, j, k, n;\n\n  dompos = domstack;\n  domend = domstack + STACKDEPTH;\n  feasible = FALSE;\n  terminate = FALSE;\n  bblevel = 1;\n  n = l-f+1;\n  if (n > a->exactn) a->exactn = n;\n\n  for (i = 0; i < n; i++) {\n    for (j = 0; j < n; j++) {\n      relation[i][j] = UNDEF;\n      for (k = LEFT; k < UNDEF; k++) {\n        domain[i][j][k] = TRUE;\n      }\n    }\n  }\n  domain[0][1][RIGHT ] = FALSE;\n  domain[0][1][ABOVE ] = FALSE;\n  domain[0][1][BEHIND] = FALSE;\n\n  recpack(a, 0, 0, n, f, UNDEF); \n  return feasible;\n}\n\n\n/* ======================================================================\n                                onebin_general\n   ====================================================================== */\n\n/* Check if boxes f..l can be packed into a single bin using general \n * packing. If \"fast\" is TRUE, the problem is only solved heuristically,\n * hence if the algorithm returns FALSE we cannot rule out the posibility\n * of packing all boxes into the bin.\n */\n\nboolean onebin_general(allinfo *a, box *f, box *l, boolean fast)\n{\n  boolean solution;\n  double t1, t2;\n\n  /* check time limit */\n  if (stopped) return FALSE;\n\n  a->iter3d = 0;\n  a->maxiter = (fast ? MAXITER : 0); /* limited or infinitly many */\n  a->exactcall++;\n\n  /* calling general pack */\n  timer(&t1);\n  solution = general_pack(a, f, l);\n  if (solution) checksol(a, f, l);\n  timer(&t2);\n  a->genertime += t2 - t1;\n  return solution;\n}\n\n\n/* ======================================================================\n\t\t\t\tenvelope\n   ====================================================================== */\n\n/* Find the two-dimensional envelope of the boxes given by extreme */\n/* points f to l. */\n\nvoid envelope(point *f, point *l, point *s1, point **sm,\n\t     itype W, itype H, itype D, itype RW, itype RH, itype cz, \n             point **ll, int *nz, stype *area)\n{\n  register point *i, *s, *t;\n  register itype x, xx, y, z, ix, iy, iz, mz;\n  register stype sum;\n\n  /* find corner points and area */\n  x = xx = z = 0; y = H; sum = 0; mz = D;\n  for (i = t = f, s = s1; i != l; i++) {\n    iz = i->z; if (iz <= cz) continue;\n    if (iz < mz) mz = iz; /* find minimum next z coordinate */\n    ix = i->x; if (ix <= x) {\n      if (iz > z) { *t = *i; t++; } \n      continue;\n    }\n    iy = i->y;\n    if ((x <= RW) && (iy <= RH)) { \n      s->x = x; s->y = iy; s->z = cz; s++; \n      sum += (x - xx) * (ptype) y; \n      y = iy; xx = x;\n    }\n    x = ix; z = iz; *t = *i; t++;\n  }\n  if (y != 0) sum += (W - xx) * (ptype) y;\n  *sm = s-1;\n  *area = sum;\n  *nz = mz;\n  *ll = t-1;\n}\n\n/* ======================================================================\n\t\t\t     checkdom\n   ====================================================================== */\n\n/* The 3D envelope is found by deriving a number of 2D envelopes. This */\n/* may however introduce some \"false\" corner points, which are marked */\n/* by the following algorithm. */\n\nvoid checkdom(point *s1, point *sl, point *sm)\n{\n  register point *s, *t, *u;\n\n  if (sl == s1-1) return;\n  for (s = s1, t = sl+1, u = sm+1; t != u; t++) {\n    while (s->x < t->x) { s++; if (s > sl) return; }\n    if ((s->x == t->x) && (s->y == t->y)) t->z = 0;\n  } \n}\n\n\n/* ======================================================================\n\t\t\t     removedom\n   ====================================================================== */\n\n/* Remove \"false\" corner points marked by algorithm \"checkdom\". */\n\npoint *removedom(point *s1, point *sl)\n{\n  register point *i, *m, *k;\n \n  for (i = k = s1, m = sl+1; i != m; i++) {\n    if (i->z == 0) continue; \n    *k = *i; k++; \n  }\n  return k-1;\n}\n\n/* ======================================================================\n\t\t\t     initboxes\n   ====================================================================== */\n\n/* Initialize boxes. Already placed boxes define extreme points, and thus */\n/* they are placed into the list fc,..,lc. Not placed boxes are used to */\n/* derive minimum dimensions. */\n\nvoid initboxes(box *f, box *l, point *fc, point **lc, \n               int *minw, int *minh, int *mind)\n{\n  register box *j, *m;\n  register point *k;\n  register int minx, miny, minz;\n\n  minx = *minw; miny = *minh; minz = *mind; \n  for (j = f, k = fc, m = l+1; j != m; j++) {\n    if (j->k) { /* defines an extreme box */\n      k->x = j->x+j->w; k->y = j->y+j->h; k->z = j->z+j->d; k++;\n    } else { /* free box */\n      if (j->w < minx) minx = j->w;\n      if (j->h < miny) miny = j->h;\n      if (j->d < minz) minz = j->d;\n    }\n  }\n  *minw = minx; *minh = miny; *mind = minz; \n  *lc = k-1;\n}\n\n\n/* ======================================================================\n\t\t\t      findplaces\n   ====================================================================== */\n\n/* Find all corner points, where a new box may be placed as well as the */\n/* volume of the \"envelope\" occupied by already placed boxes. Already */\n/* placed boxes are given by f,..,l, while a list of possible placings */\n/* is returned in s1,..,sm. The return value of the procedure is an upper */\n/* bound on the possible filling of this bin. */\n\nstype findplaces(allinfo *a, box *f, box *l, \n                 point *s1, point **sm, stype fill)\n{\n  register point *k;\n  int minw, minh, mind, W, H, D, RW, RH, z, zn;\n  point *sk, *lc, *sl, *st, *s0;\n  stype vol, area;\n  point fc[MAXBOXES+1];\n\n  /* select boxes which are chosen, and find min dimensions of unchosen */\n  minw = W = a->W; minh = H = a->H; mind = D = a->D; \n  initboxes(f, l, fc, &lc, &minw, &minh, &mind);\n\n  /* sort the boxes according to max y (first) max x (second) */\n  if (lc >= fc) psortdecr(fc, lc); /* order decreasing */\n\n  /* for each z-coordinate find the 2D envelope */\n  vol = 0; sl = s1-1; sk = s1; s0 = NULL;\n  RW = W - minw; RH = H - minh; \n  lc++; k = lc; k->x = W+1; k->y = 0; k->z = a->D+1;\n  for (z = 0; z != D; z = zn) {\n    /* find 2D envelope for all boxes which cover *z */\n    envelope(fc, lc+1, sl+1, &st, W, H, D, RW, RH, z, &lc, &zn, &area);\n    if (zn + mind > D) zn = D; /* nothing fits between zn and D */\n    vol += area * (ptype) (zn - z); /* update volume */\n    checkdom(sk, sl, st); /* check for dominance */\n    sk = sl+1; sl = st; \n    if (z == 0) s0 = sl;\n  }\n  *sm = removedom(s0+1, sl);\n  return fill + (a->BVOL - vol); /* bound is curr filling + all free vol */\n}\n\n\n/* ======================================================================\n\t\t\t\tbranch\n   ====================================================================== */\n\n/* Recursive algorithm for solving a knapsack filling of a single bin. */\n/* In each iteration, the set of feasible positions for placing a new */\n/* box is found, and an upper bound on the filling is derived. If the */\n/* bound indicates that an improved solution still may be obtained, every */\n/* box is tried to be placed at every feasible position, before calling */\n/* the algorithm recursively. */\n\nvoid branch(allinfo *a, box *f, box *l, int miss, stype fill)\n{\n  register box *i;\n  register point *s;\n  int d;\n  stype bound;\n  point *sl, s1[9*MAXBOXES];\n\n  if (stopped) return;\n  a->iter3d++;\n  if ((a->iter3d == a->maxiter) && (a->maxiter != 0)) terminate = TRUE;\n  if (a->iter3d % 1000 == 0) check_timelimit(a->timelimit);\n  a->subiterat++; \n  if (a->subiterat == IUNIT) { \n    a->subiterat = 0;\n    a->iterat++; check_iterlimit(a->iterat, a->iterlimit);\n  }\n  if (terminate) return;\n\n  /* find min/max dimensions of remaining boxes */\n  if (miss == 0) {\n    /* none left -> good: save solution */\n    memcpy(a->fsol, f, sizeof(box) * DIF(f,l));\n    a->maxfill = a->BVOL; terminate = TRUE; a->miss = miss;\n  } else {\n    /* check if better filling */\n    if (fill > a->maxfill) {\n      memcpy(a->fsol, f, sizeof(box) * DIF(f,l)); \n      a->maxfill = fill; a->miss = miss;\n    }\n\n    /* find bound and positions to place new boxes */\n    bound = findplaces(a, f, l, s1, &sl, fill);\n\n    if (bound > a->maxfill) {\n      /* for each position in S, try to place an box there */\n      for (s = s1; s != sl+1; s++) {\n\tfor (i = f, d = 0; i != l+1; i++) {\n\t  if (i->k) continue; /* already chosen */\n\n\t  /* see if box fits at position s */\n\t  if ((int) (s->x) + i->w > a->W) continue;\n\t  if ((int) (s->y) + i->h > a->H) continue;\n\t  if ((int) (s->z) + i->d > a->D) continue;\n\n\t  /* place box and call recursively */\n\t  i->k = TRUE; i->x = s->x; i->y = s->y; i->z = s->z;\n\t  branch(a, f, l, miss - 1, fill + i->vol);\n\t  i->k = FALSE; i->x = i->y = i->z = 0; d++;\n\t  if (d == a->mcut) break; /* terminate after mcut branches */\n\t  if (terminate) break;\n\t}\n      }\n    }\n  }\n}\n\n\n/* ======================================================================\n\t\t\t\tmcut_heuristic\n   ====================================================================== */\n\n/* Knapsack filling of a single bin, solved heuristically. The heuristic */\n/* is based on the exact algorithm for knapsack filling a single bin, where */\n/* only a limited number of sub-nodes are considered at every branching node */\n/* (the so-called m-cut approach) */\n \nvoid mcut_heuristic(allinfo *a)\n{\n  box *f, *l, *i, *j, *m;\n  int b, n;\n\n  /* initialize boxes */\n  for (i = a->fbox, m = a->lbox+1; i != m; i++) {\n    i->bno = i->x = i->y = i->z = 0; i->k = FALSE;\n  }\n\n  /* fill bins one by one */\n  f = a->fbox; l = a->lbox;\n  for (b = 1; ; b++) {\n    /* fill one bin */\n    for (i = f; i <= l; i++) i->k = FALSE; /* box not chosen */\n    a->iter3d = 0;\n    a->maxfill = 0;\n    a->miss = DIF(f,l);\n    a->maxiter = 5*MAXITER;\n    terminate = FALSE;\n    n = DIF(f,l);\n    a->mcut = 2;\n    if (n < 15) a->mcut = 3;\n    if (n < 10) a->mcut = 4;\n    branch(a, f, l, n, 0);\n\n    /* copy solution */\n    for (i = a->fsol, j = f, m = l+1; j != m; i++, j++) *j = *i;\n\n    /* remove chosen boxes */\n    for (i = f; i <= l;) if (i->k) { i->bno = b; SWAP(i,l); l--; } else i++;\n\n    /* check if finished */\n    if (l == f-1) break;\n  }\n  if (b < a->zmcut) a->zmcut = b; /* save solution */\n  if (a->zmcut < a->z) savesol(a, a->fbox, a->lbox, a->zmcut);\n}\n\n\n/* ======================================================================\n\t\t\t\tmcut3_heuristic\n   ====================================================================== */\n\n/* Knapsack filling of a single bin, solved heuristically. Three different */\n/* rotations of the problem are considered, and the best found solution */\n/* is selected. */\n\nvoid mcut3_heuristic(allinfo *a)\n{\n  int i;\n  double t1, t2;\n \n  timer(&t1); \n  a->zmcut = a->n;  /* very bad lower bound */\n  for (i = WDIM; i <= DDIM; i++) {\n    mcut_heuristic(a);\n    rotate_solution(a, a->fopt, a->lopt);\n    rotate_problem(a, a->fbox, a->lbox);\n  }\n  timer(&t2);\n  a->mhtime = t2 - t1;\n}\n\n\n/* **********************************************************************\n   **********************************************************************\n \t       branch-and-bound for 3D bin-packing problem\n   **********************************************************************\n   ********************************************************************** */\n\n\n/* ======================================================================\n\t\t\t\t fits\n   ====================================================================== */\n\n/* The routine \"fitsm\" checks whether a given subset of boxes fits into */\n/* a single bin. To improve performance, specialized algorithms are derived */\n/* for cases with two to three boxes */\n\nboolean fits2(box *i, box *j, itype W, itype H, itype D)\n{\n  /* all coordinates are initialized to zero, so just adjust changes! */\n  /* the 2-box solution is always guillotine cuttable */\n  if (i->w + j->w <= W) { j->x = i->w; return TRUE; }\n  if (i->h + j->h <= H) { j->y = i->h; return TRUE; }\n  if (i->d + j->d <= D) { j->z = i->d; return TRUE; }\n  return FALSE;\n}\n\n\nboolean fits2p(box *i, box *j, itype W, itype H, itype D)\n{\n  if (i->w + j->w <= W) return TRUE;\n  if (i->h + j->h <= H) return TRUE;\n  if (i->d + j->d <= D) return TRUE;\n  return FALSE;\n}\n\n\nboolean fits3(box *i, box *j, box *k, itype W, itype H, itype D)\n{\n  box *t;\n  itype w, h, d, r;\n\n  /* all coordinates are initialized to zero, so just adjust changes! */\n  /* the 3-box solution can either be cut by guillotine cuts */\n  for (r = 1; r <= 3; r++) {\n    /* cut (i,j) and (k) in one of three dimensions */\n    w = W - k->w; h = H - k->h; d = D - k->d; \n    if ((i->w<=w) && (j->w<=w) && fits2(i,j,w,H,D)) { k->x = w; return TRUE; } \n    if ((i->h<=h) && (j->h<=h) && fits2(i,j,W,h,D)) { k->y = h; return TRUE; } \n    if ((i->d<=d) && (j->d<=d) && fits2(i,j,W,H,d)) { k->z = d; return TRUE; } \n    t = i; i = j; j = k; k = t;\n  }\n\n\n  /* (xi,yi,zi) = (0,0,0); (xj,yj,zj) = (wi,0,0); (xk,yk,zk) = (0,hi,dj) */\n  if ((i->w+j->w <= W) && (i->h+k->h <= H) && (j->d+k->d <= D)) {\n    j->x = i->w; k->y = i->h; k->z = j->d; return TRUE;\n  } \n  /* (xi,yi,zi) = (0,0,0); (xj,yj,zj) = (wk,0,di); (xk,yk,zk) = (0,hi,0) */\n  if ((j->w+k->w <= W) && (i->h+k->h <= H) && (i->d+j->d <= D)) {\n    j->x = k->w; j->z = i->d; k->y = i->h; return TRUE;\n  } \n  /* (xi,yi,zi) = (0,0,0); (xj,yj,zj) = (0,hi,dk); (xk,yk,zk) = (wi,0,0) */\n  if ((i->w+k->w <= W) && (i->h+j->h <= H) && (k->d+j->d <= D)) {\n    j->y = i->h; j->z = k->d; k->x = i->w; return TRUE;\n  } \n  /* (xi,yi,zi) = (0,0,0); (xj,yj,zj) = (0,hi,0); (xk,yk,zk) = (wj,0,di) */\n  if ((j->w+k->w <= W) && (i->h+j->h <= H) && (k->d+i->d <= D)) {\n    j->y = i->h; k->x = j->w; k->z = i->d; return TRUE;\n  } \n  /* (xi,yi,zi) = (0,0,0); (xj,yj,zj) = (wi,0,0); (xk,yk,zk) = (0,hj,di) */ \n  if ((i->w+j->w <= W) && (j->h+k->h <= H) && (i->d+k->d <= D)) { \n    j->x = i->w; k->y = j->h; k->z = i->d; return TRUE;\n  } \n  /* (xi,yi,zi) = (0,0,0); (xj,yj,zj) = (0,0,di); (xk,yk,zk) = (wi,hj,0) */ \n  if ((i->w+k->w <= W) && (j->h+k->h <= H) && (i->d+j->d <= D)) { \n    j->z = i->d; k->x = i->w; k->y = j->h; return TRUE;\n  } \n  return FALSE; \n}  \n\n\nboolean fitsm(allinfo *a, box *t, box *k, boolean fast)\n{\n  boolean fits;\n  ntype lb;\n\n  lb = bound_two(a, t, k);\n  if (lb > 1) return FALSE;\n  a->exfill++; fits = FALSE;\n  fits = onebin_general(a, t, k, fast); \n  return fits;\n}\n\n\n/* ======================================================================\n\t\t\t\tonebin_decision\n   ====================================================================== */\n\n/* The following procedure checks whether a new box \"j\" fits into the the */\n/* bin \"bno\" (together with already placed boxes in the bin). If the answer */\n/* is \"no\" then definitly it is not possible to place the box into the bin. */\n\nboolean onebin_decision(allinfo *a, box *j, int bno)\n{\n  register box *i, *k, *m;\n  box t[MAXBOXES];\n  boolean fits;\n  int size;\n\n  for (i = a->fbox, m = j, k = t-1; i != m; i++) {\n    if (i->bno == bno) { k++; *k = *i; k->x = k->y = k->z = 0; k->ref = i; }\n  }\n  k++; *k = *j; k->x = k->y = k->z = 0; k->ref = j; k->k = TRUE; \n\n  size = DIF(t,k); \n  switch (size) {\n    case 0: error(\"no boxes in onebin_decision\");\n    case 1: fits = TRUE; k->x = k->y = k->z = 0; break;\n    case 2: fits = fits2(t, k, a->W, a->H, a->D); break;\n    case 3: fits = fits3(t, t+1, k, a->W, a->H, a->D); break;\n    default: fits = fitsm(a, t, k, FALSE); break;\n  }\n  if (size <= 3) {\n    a->subiterat++; \n    if (a->subiterat == IUNIT) { \n      a->subiterat = 0;\n      a->iterat++; check_iterlimit(a->iterat, a->iterlimit);\n    }\n  }\n\n  if (fits) {\n    /* copy solution back */\n    for (i = t, m = k+1; i != m; i++) {\n      k = i->ref; k->x = i->x; k->y = i->y; k->z = i->z; k->k = TRUE;\n    }\n  }\n  return fits;\n}\n\n\n/* ======================================================================\n\t\t\t\tonebin_heuristic\n   ====================================================================== */\n\n/* This is a heuristic version of \"onebin_decision\". If the answer is \"yes\" */\n/* then a heuristic solution has been found where boxes f,..,l fit into a  */\n/* bin. If the answer is \"no\" then a filling may still be possible, but it */\n/* was not found by the heuristic. */ \n\nboolean onebin_heuristic(allinfo *a, box *f, box *l)\n{\n  box *i, *m;\n  boolean fits;\n \n  for (i = f, m = l+1; i != m; i++) { i->x = i->y = i->z = 0; }\n  switch (DIF(f,l)) {\n    case 0: error(\"no boxes in onebin_heuristic\");\n    case 1: fits = TRUE; break;\n    case 2: fits = fits2(f, l, a->W, a->H, a->D); break;\n    case 3: fits = fits3(f, f+1, l, a->W, a->H, a->D); break;\n    default: fits = fitsm(a, f, l, TRUE); break;\n  }\n  return fits;\n}\n\n\n/* ======================================================================\n\t\t\t\ttry_close\n   ====================================================================== */\n\n/* A bin may be closed if no more boxes fit into it. The present version */\n/* uses a more advanced criterion: First, the set of boxes which fit into */\n/* bin \"bno\" is derived. This is done, by testing whether each box (alone) */\n/* fits together with the already placed boxes in the bin. Having derived */\n/* the set of additional boxes that (individually) fits into the bin, it is */\n/* tested whether a solution exists where all the additional boxes are */\n/* placed in the bin. If this is the case, we have found a optimal placing */\n/* of the additional boxes, and thus we may close the bin. */\n\nboolean try_close(allinfo *a, box **curr, ntype bno,\n                 box *oldf, box **oldl, box **oldlc, ntype *oldnoc, \n                 boolean *oldclosed, int level)\n{\n  register box *j, *m, *k, *r, *i, *s;\n  register stype vol;\n  box f[MAXBOXES];\n  ntype h, n, b;\n  boolean didclose, fits;\n\n  if (level > MAXCLOSE) return FALSE;\n  i = *curr; didclose = FALSE;\n  for (b = 1; b <= bno; b++) {\n    if (i > a->lbox) break;\n    if (a->closed[b]) continue;\n    for (j = a->fbox, m = i, k = f, n = 0, vol = 0; j != m; j++) {\n      if (j->bno == b) { *k = *j; k->ref = j; k++; n++; vol += j->vol; }\n    }\n    if (n == 0) error(\"bin with no boxes\");\n    if (vol < a->BVOL/2) continue;\n    for (j = i, h = 0, m = a->lbox+1; j != m; j++) {\n      if ((j->no > a->n) || (j->no < 1)) error(\"bad no\");\n      fits = onebin_decision(a, j, b);\n      if (fits) { *k = *j; k->ref = j; k++; h++; vol += j->vol; }\n      if (vol > a->BVOL) break;\n    }\n    if (vol > a->BVOL) continue;\n    if (onebin_heuristic(a, f, k-1)) {\n      if (!didclose) { /* take backup of table when first bin closed */\n        memcpy(oldclosed, a->closed, sizeof(boolean)*(bno+1));\n        memcpy(oldf, a->fbox, sizeof(box)*DIF(a->fbox,a->lbox));\n        *oldl = a->lbox; *oldlc = a->lclosed; *oldnoc = a->noc;\n      }\n      a->closed[b] = TRUE; s = a->lclosed; didclose = TRUE;\n      a->noc++; if (a->noc > a->maxclose) a->maxclose = a->noc;\n      for (j = f; j != k; j++) { \n        r = j->ref; r->bno = b; r->k = TRUE; \n        r->x = j->x; r->y = j->y; r->z = j->z; \n      }\n      for (j = k = a->fbox, m = a->lbox+1; j != m; j++) {\n        if (j->bno == b) { s++; *s = *j; } else { *k = *j; k++; } \n      }\n      a->lbox = k-1; a->lclosed = s;\n      i -= n; /* reposition current box */\n    }\n  }\n  *curr = i;\n  return didclose;\n}\n\n\n/* ======================================================================\n                                free_close\n   ====================================================================== */\n\n/* Reopen a closed bin when backtracking. */\n\nvoid free_close(allinfo *a, ntype bno, \n                box *oldf, box *oldl, box *oldlc, ntype oldnoc,\n                boolean *oldclosed)\n{\n  a->lbox = oldl; a->lclosed = oldlc; a->noc = oldnoc;\n  memcpy(a->fbox, oldf, sizeof(box)*DIF(a->fbox,oldl));\n  memcpy(a->closed, oldclosed, sizeof(boolean)*(bno+1));\n}\n\n\n/* ======================================================================\n\t\t\t\trec_binpack\n   ====================================================================== */\n\n/* Recursive algorithm for 3D Bin-packing Problem. In each iteration, the */\n/* next box \"i\" is assigned to every open bin, as well as to a new bin. */\n\nvoid rec_binpack(allinfo *a, box *i, int bno, ntype lb, int level)\n{\n  box of[MAXBOXES], *ol, *ox;\n  boolean ocl[MAXBOXES];\n  ntype b, oc;\n  boolean more;\n\n  if (bno >= a->z) return; /* used too many bins */\n  if (a->z == a->lb) return; /* optimal solution found */\n  a->subnodes++;\n  if (a->subnodes == IUNIT) { a->subnodes = 0; a->nodes++; }\n  check_nodelimit(a->nodes, a->nodelimit);\n  check_iterlimit(a->iterat, a->iterlimit);\n  check_timelimit(a->timelimit);\n  if (stopped) return;\n\n  if (i == a->lbox+1) {\n    /* all boxes assigned, must be better solution */\n    savesol(a, a->fbox, a->lbox, bno);\n  } else {\n    more = try_close(a, &i, bno, of, &ol, &ox, &oc, ocl, level);\n    if (i == a->lbox+1) { /* all boxes went into closed bins */\n      savesol(a, a->fbox, a->lbox, bno);\n    } else {\n      if (more) lb = a->noc + bound_two(a, a->fbox, a->lbox);\n      if (lb < a->z) {\n        for (b = 1; b <= bno; b++) {\n          if (a->closed[b]) continue; /* cannot add to closed bin */\n            if (onebin_decision(a, i, b)) {\n  \t    i->bno = b;\n\t    rec_binpack(a, i+1, bno, lb, level+1);\n\t    i->bno = 0;\n  \t  }\n        }\n        i->bno = bno+1; i->x = i->y = i->z = 0;\n        a->closed[i->bno] = FALSE;\n        rec_binpack(a, i+1, bno+1, lb, level+1);\n        i->bno = 0;\n      }\n    }\n    /* restore */\n    if (more) free_close(a, bno, of, ol, ox, oc, ocl);\n  }\n}\n\n\n/* **********************************************************************\n   **********************************************************************\n\t\t\t     Main procedure\n   **********************************************************************\n   ********************************************************************** */\n\n/* ======================================================================\n\t\t\t\tclearboxes\n   ====================================================================== */\n\nvoid clearboxes(allinfo *a)\n{\n  box *i, *m;\n\n  for (i = a->fbox, m = a->lbox+1; i != m; i++) {\n    i->x = i->y = i->z = i->bno = 0; i->k = FALSE; i->vol = VOL(i);\n  }\n  /* sort nonincreasing volume */\n  qsort(a->fbox, (m-a->fbox), sizeof(box), (funcptr) vcomp);\n}\n\n\n/* ======================================================================\n\t\t\t\tcopyboxes\n   ====================================================================== */\n\nvoid copyboxes(allinfo *a, int *w, int *h, int *d, int W, int H, int D)\n{\n  box *i, *m;\n  int k;\n\n  for (i = a->fbox, m = a->lbox+1, k = 0; i != m; i++, k++) {\n    i->no = k+1; i->w = w[k]; i->h = h[k]; i->d = d[k];\n    if ((w[k] < 1) || (w[k] > W)) error(\"bad w\\n\");\n    if ((h[k] < 1) || (h[k] > H)) error(\"bad h\\n\");\n    if ((d[k] < 1) || (d[k] > D)) error(\"bad d\\n\");\n  }\n\n  clearboxes(a);\n}\n\n\n/* ======================================================================\n\t\t\t\treturnboxes\n   ====================================================================== */\n\nvoid returnboxes(allinfo *a, int *x, int *y, int *z, int *bno)\n{\n  box *i, *m;\n  int k;\n\n  for (i = a->fopt, m = a->lopt+1; i != m; i++) {\n    k = i->no-1; x[k] = i->x; y[k] = i->y; z[k] = i->z; bno[k] = i->bno;\n  }\n}\n\n\n/* ======================================================================\n\t\t\t\tbinpack3d\n   ====================================================================== */\n\nvoid binpack3d(int n, int W, int H, int D,\n               int *w, int *h, int *d, \n               int *x, int *y, int *z, int *bno,\n               int *lb, int *ub, \n               int nodelimit, int iterlimit, int timelimit, \n               int *nodeused, int *iterused, int *timeused)\n{\n  allinfo a;\n  box t0[MAXBOXES], t1[MAXBOXES], t2[MAXBOXES], t3[MAXBOXES];\n  boolean cl[MAXBOXES];\n  \n  /* start the timer */ \n  timer(NULL); stopped = FALSE; \n \n  /* copy info to a structure */\n  if (n+1 > MAXBOXES) error(\"too big instance\");\n  a.n = n; a.W = W; a.H = H; a.D = D;\n  a.fbox     = t0; \n  a.lbox     = a.fbox + a.n - 1; \n  a.fsol     = t1;\n  a.lsol     = a.fsol + a.n - 1;\n  a.fopt     = t2;\n  a.lopt     = a.fopt + a.n - 1;\n  a.fclosed  = t3;\n  a.lclosed  = a.fclosed - 1;\n  a.noc      = 0;\n  a.closed   = cl;\n  a.BVOL     = W * (ptype) H * D;\n  a.maxfill  = 0;\n  a.exfill   = 0;\n  a.nodelimit= 0;\n  a.iterlimit= 0;\n  a.timelimit= 0;\n  a.nodes    = 0;\n  a.subnodes = 0;\n  a.iterat   = 0;\n  a.subiterat= 0;\n  a.didpush  = 0;\n  a.maxclose = 0;\n  a.genertime= 0;\n  a.robottime= 0;\n  a.z        = a.n+1;\n\n  /* copy boxes to internal structure */\n  copyboxes(&a, w, h, d, W, H, D);\n\n  /* find bounds */\n  a.bound0 = bound_zero(&a, a.fbox, a.lbox);\n  a.bound1 = bound_one(&a, a.fbox, a.lbox);\n  a.bound2 = bound_two(&a, a.fbox, a.lbox);\n  a.lb = a.bound2;\n\n  /* find heuristic solution */\n  dfirst3_heuristic(&a);\n\n  /* initialize search limits for exact search */\n  a.nodelimit= nodelimit;\n  a.iterlimit= iterlimit;\n  a.timelimit= timelimit;\n\n  /* outer tree enummeration */\n  clearboxes(&a); /* clear positions */\n  rec_binpack(&a, a.fbox, 0, a.lb, 1);\n  timer(&(a.time));\n\n  /* check found solution */\n  /* checksol(&a, a.fopt, a.lopt); */\n\n  /* copy boxes back to arrays */\n  returnboxes(&a, x, y, z, bno);\n  *ub = a.z;\n  *lb = (stopped ? a.lb : a.z);\n  *nodeused = a.nodes;\n  *iterused = a.iterat;\n  *timeused = a.time * 1000;\n}\n\n\n"
  },
  {
    "path": "pyshipping/__init__.py",
    "content": "\"\"\"pyShipping contains routines related to shipping and warehousing.\"\"\"\n"
  },
  {
    "path": "pyshipping/addressvalidation.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n\"\"\"\naddressvalidation.py - check the validity of addresses\n\nShould once integrate with\nhttp://www.deutschepost.de/dpag?tab=1&skin=hi&check=yes&lang=de_DE&xmlFile=link1015574_1021\nhttp://www.isogmbh.de/leistungen/dataquality-management/adressvalidierung.html\nor http://opengeodb.hoppe-media.com/\n\n\nCreated by Maximillian Dornseif on 2009-09-03.\nCopyright (c) 2009, 2010 HUDORA. All rights reserved.\n\"\"\"\n\nimport unittest\n\n\ndef validate(adr, servicelevel=1):\n    \"\"\"Validates an address and returns a possibly corrected address.\n\n    'adr' should be a object conforming to the address protocol\n        - see http://cybernetics.hudora.biz/projects/wiki/AddressProtocol\n    'servicelevel' can be an integer with the following values:\n    1 - generic validation, no money/effort should be spend on correction and suggestions\n    2 - TBD.\n\n    returns (status, message, [corrected addresses and variants])\n\n    status can be:\n    '10invalid' - address is for sure non deliverable in this form\n    '20troubled' - bounced before or is unlikely to be correct - possible alternatives are returned\n    '30ok' - likely to work\n    '31ok' - likely to work but was corrected\n    '40verified' - we are sure it works\n    \"\"\"\n\n    adr['land'] = adr['land'].strip()\n    adr['plz'] = adr['plz'].strip()\n\n    if adr['land'] != 'IE' and not adr['plz']:\n        return ('10invalid', 'Postleitzahl fehlt', [adr])\n\n    if adr['land'] == 'DE' and len(adr.get('plz', '')) != 5:\n        return ('10invalid', 'Postleitzahl fehlerhaft', [adr])\n\n    return ('30ok', '', [adr])\n\n\nclass AddressvalidationTests(unittest.TestCase):\n    \"\"\"Tests for the address validation facility.\"\"\"\n\n    def setUp(self):\n        \"\"\"Set up test address base.\"\"\"\n        self.address = {'name1': 'HUDORA GmbH',\n                        'name2': 'Abt. Cybernetics',\n                        'strasse': 'Jägerwald 13',\n                        'land': 'DE',\n                        'plz': '42897',\n                        'ort': 'Remscheid',\n                        'tel': '+49 2191 60912 0',\n                        'fax': '+49 2191 60912 50',\n                        'mobil': '+49 175 00000xx',\n                        'email': 'nobody@hudora.de'}\n\n    def test_good_address(self):\n        \"\"\"Test if correct addresses are considered correct.\"\"\"\n        self.assertEqual(validate(self.address)[0], '30ok')\n        self.assertEqual(validate(self.address)[1], '')\n\n    def test_missing_zip(self):\n        \"\"\"Test if correct addresses are considered correct.\"\"\"\n        self.address['plz'] = ''\n        self.assertEqual(validate(self.address)[0], '10invalid')\n\n    def test_short_zip(self):\n        \"\"\"Test if correct addresses are considered correct.\"\"\"\n        self.address['plz'] = '123'\n        self.assertEqual(validate(self.address)[0], '10invalid')\n\n    def test_long_zip(self):\n        \"\"\"Test if correct addresses are considered correct.\"\"\"\n        self.address['plz'] = '12345 Rade'\n        self.assertEqual(validate(self.address)[0], '10invalid')\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "pyshipping/binpack.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n\"\"\"\nbinpack.py\n\nCreated by Maximillian Dornseif on 2010-08-16.\nCopyright (c) 2010 HUDORA. All rights reserved.\n\"\"\"\n\n\nimport binpack_simple\n\n\ndef binpack(packages, bin=None, iterlimit=5000):\n    return binpack_simple.binpack(packages, bin, iterlimit)\n\n\ndef test(func):\n    import time\n    from package import Package\n    fd = open('testdata.txt')\n    vorher = 0\n    nachher = 0\n    start = time.time()\n    counter = 0\n    for line in fd:\n        counter += 1\n        if counter > 450:\n            break\n        packages = [Package(pack) for pack in line.strip().split()]\n        if not packages:\n            continue\n        bins, rest = func(packages)\n        if rest:\n            print \"invalid data\", rest, line\n        else:\n            vorher += len(packages)\n            nachher += len(bins)\n    print time.time() - start,\n    print vorher, nachher, float(nachher) / vorher * 100\n\n\nif __name__ == '__main__':\n    print \"py\",\n    test(binpack)\n\n\nimport time\nfrom pyshipping.package import Package\n"
  },
  {
    "path": "pyshipping/binpack_simple.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n\"\"\"\nbinpack_simple.py\n\nThis code implemnts 3D bin packing in pure Python\n\nBin packing in this context is calculating the best way to store a number of differently sized boxes in a\nnumber of fixed sized \"bins\". It is what usually happens in a Warehouse bevore shipping.\n\nThe Algorithm has a simple fit first approach, but can archive relative good results because it tries\ndifferent rectangular rotations of the packages. Since the Algorithm can't interate over all possible\ncombinations we use a heuristic approach.\n\nFor a few dozen packages it reaches adaequate runtime. Below are the results calculated about a set of\n500 real world packing problems.\n\nBinsize     Runtime                 Recuction in shipped Packages\n600x400x400 31.5993559361 4970 2033 40.9054325956\n600x445x400 31.5596890450 4970 1854 37.3038229376\n600x500x400 29.1432909966 4970 1685 33.9034205231\n\n\nOn the datasets we operate on we can archive comparable preformance to academic higly optimized C code\nlike David Pisinger's 3bpp:\n\n     Runtime                 Recuction in shipped Packages\npy   11.3468761444 2721 1066 39.1767732451\n3bpp 9.95857691765 2721 1086 39.9117971334\n\nThe Python implementation is somewhat slower but can archive slightly better packing results on our\ndatasets.\n\n\nCreated by Maximillian Dornseif on 2010-08-14.\nCopyright (c) 2010 HUDORA. All rights reserved.\n\"\"\"\n\n\nimport time\nimport random\n\n\ndef packstrip(bin, p):\n    \"\"\"Creates a Strip which fits into bin.\n\n    Returns the Packages to be used in the strip, the dimensions of the strip as a 3-tuple\n    and a list of \"left over\" packages.\n    \"\"\"\n    # This code is somewhat optimized and somewhat unreadable\n    s = []                # strip\n    r = []                # rest\n    ss = sw = sl = 0      # stripsize\n    bs = bin.heigth       # binsize\n    sapp = s.append       # speedup\n    rapp = r.append       # speedup\n    ppop = p.pop          # speedup\n    while p and (ss <= bs):\n        n = ppop(0)\n        nh, nw, nl = n.size\n        if ss + nh <= bs:\n            ss += nh\n            sapp(n)\n            if nw > sw:\n                sw = nw\n            if nl > sl:\n                sl = nl\n        else:\n            rapp(n)\n    return s, (ss, sw, sl), r + p\n\n\ndef packlayer(bin, packages):\n    strips = []\n    layersize = 0\n    layerx = 0\n    layery = 0\n    binsize = bin.width\n    while packages:\n        strip, (sizex, stripsize, sizez), rest = packstrip(bin, packages)\n        if layersize + stripsize <= binsize:\n            packages = rest\n            if not strip:\n                # we were not able to pack anything\n                break\n            layersize += stripsize\n            layerx = max([sizex, layerx])\n            layery = max([sizez, layery])\n            strips.extend(strip)\n        else:\n            # Next Layer please\n            packages = strip + rest\n            break\n    return strips, (layerx, layersize, layery), packages\n\n\ndef packbin(bin, packages):\n    packages.sort()\n    layers = []\n    contentheigth = 0\n    contentx = 0\n    contenty = 0\n    binsize = bin.length\n    while packages:\n        layer, (sizex, sizey, layersize), rest = packlayer(bin, packages)\n        if contentheigth + layersize <= binsize:\n            packages = rest\n            if not layer:\n                # we were not able to pack anything\n                break\n            contentheigth += layersize\n            contentx = max([contentx, sizex])\n            contenty = max([contenty, sizey])\n            layers.extend(layer)\n        else:\n            # Next Bin please\n            packages = layer + rest\n            break\n    return layers, (contentx, contenty, contentheigth), packages\n\n\ndef packit(bin, originalpackages):\n    packedbins = []\n    packages = sorted(originalpackages)\n    while packages:\n        packagesinbin, (binx, biny, binz), rest = packbin(bin, packages)\n        if not packagesinbin:\n            # we were not able to pack anything\n            break\n        packedbins.append(packagesinbin)\n        packages = rest\n    # we now have a result, try to get a better result by rotating some bins\n\n    return packedbins, rest\n\n\n# In newer Python versions these van be imported:\n# from itertools import permutations\ndef product(*args, **kwds):\n    # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy\n    # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111\n    pools = map(tuple, args) * kwds.get('repeat', 1)\n    result = [[]]\n    for pool in pools:\n        result = [x + [y] for x in result for y in pool]\n    for prod in result:\n        yield tuple(prod)\n\n\ndef permutations(iterable, r=None):\n    pool = tuple(iterable)\n    n = len(pool)\n    r = n if r is None else r\n    for indices in product(range(n), repeat=r):\n        if len(set(indices)) == r:\n            yield tuple(pool[i] for i in indices)\n\n\nclass Timeout(Exception):\n    pass\n\n\ndef allpermutations_helper(permuted, todo, maxcounter, callback, bin, bestpack, counter):\n    if not todo:\n        return counter + callback(bin, permuted, bestpack)\n    else:\n        others = todo[1:]\n        thispackage = todo[0]\n        for dimensions in set(permutations((thispackage[0], thispackage[1], thispackage[2]))):\n            thispackage = Package(dimensions, nosort=True)\n            if thispackage in bin:\n                counter = allpermutations_helper(permuted + [thispackage], others, maxcounter, callback,\n                                                 bin, bestpack, counter)\n            if counter > maxcounter:\n                raise Timeout('more than %d iterations tries' % counter)\n        return counter\n\n\ndef trypack(bin, packages, bestpack):\n    bins, rest = packit(bin, packages)\n    if len(bins) < bestpack['bincount']:\n        bestpack['bincount'] = len(bins)\n        bestpack['bins'] = bins\n        bestpack['rest'] = rest\n    if bestpack['bincount'] < 2:\n        raise Timeout('optimal solution found')\n    return len(packages)\n\n\ndef allpermutations(todo, bin, iterlimit=5000):\n    random.seed(1)\n    random.shuffle(todo)\n    bestpack = dict(bincount=len(todo) + 1)\n    try:\n        # First try unpermuted\n        trypack(bin, todo, bestpack)\n        # now try permutations\n        allpermutations_helper([], todo, iterlimit, trypack, bin, bestpack, 0)\n    except Timeout:\n        pass\n    return bestpack['bins'], bestpack['rest']\n\n\ndef binpack(packages, bin=None, iterlimit=5000):\n    \"\"\"Packs a list of Package() objects into a number of equal-sized bins.\n\n    Returns a list of bins listing the packages within the bins and a list of packages which can't be\n    packed because they are to big.\"\"\"\n    if not bin:\n        bin = Package(\"600x400x400\")\n    return allpermutations(packages, bin, iterlimit)\n\n\ndef test():\n    fd = open('testdata.txt')\n    vorher = 0\n    nachher = 0\n    start = time.time()\n    for line in fd:\n        packages = [Package(pack) for pack in line.strip().split()]\n        if not packages:\n            continue\n        bins, rest = binpack(packages)\n        if rest:\n            print \"invalid data\", rest, line\n        else:\n            vorher += len(packages)\n            nachher += len(bins)\n    print time.time() - start,\n    print vorher, nachher, float(nachher) / vorher * 100\n\n\nif __name__ == '__main__':\n    import cProfile\n    cProfile.run('test()')\n\n\nfrom pyshipping.package import Package\n"
  },
  {
    "path": "pyshipping/carriers/__init__.py",
    "content": "\"\"\"Function for specific freight carriers.\"\"\"\n"
  },
  {
    "path": "pyshipping/carriers/dpd/__init__.py",
    "content": "\"\"\"Functions specific to DPD/Geopost.\"\"\"\n"
  },
  {
    "path": "pyshipping/carriers/dpd/georoute.py",
    "content": "#!/usr/bin/env python\n# -*- encoding: utf-8 -*-\n\"\"\"\ngeoroute.py - get DPD related routng information\n\nOriginally coded by md, cleand up and extended by jmv and then again reworked by md.\nCopyright 2006, 2007 HUDORA GmbH. Published under a BSD License.\n\"\"\"\n\nimport os\nimport os.path\nimport gzip\nimport logging\nimport sqlite3\n\n\nROUTETABLES_BASE = os.path.join(os.path.split(os.path.abspath(__file__))[0], 'georoutetables')\nROUTES_DB_BASE = '/tmp/dpdroutes'\n\n\n# Quelle: http://de.wikipedia.org/wiki/Liste_der_Kfz-Nationalitätszeichen\nISO2CAR = {\n    'AT': 'A',\n    'BE': 'B',\n    'FR': 'F',\n}\n\n\nclass InvalidFormatError(Exception):\n    \"\"\"Invalid input file format.\"\"\"\n    pass\n\n\nclass GeorouteException(Exception):\n    \"\"\"Base class for all routing exceptions\"\"\"\n    pass\n\n\nclass CountryError(GeorouteException):\n    \"\"\"Unknown country.\"\"\"\n    pass\n\n\nclass DepotError(GeorouteException):\n    \"\"\"Unknown depot.\"\"\"\n    pass\n\n\nclass ServiceError(GeorouteException):\n    \"\"\"Unknown service.\"\"\"\n    pass\n\n\nclass TranslationError(GeorouteException):\n    \"\"\"Cannot translate city and country to postcode.\"\"\"\n    pass\n\n\nclass RoutingDepotError(GeorouteException):\n    \"\"\"Unknown routing depot.\"\"\"\n    pass\n\n\nclass NoRouteError(GeorouteException):\n    \"\"\"Route not found.\"\"\"\n    pass\n\n\nclass Parcel(object):\n    \"\"\"Parcel destination data.\"\"\"\n\n    # depreciated\n    def __init__(self, depot='142', service='101', country='DE', city=None, postcode=None):\n        import warnings\n        warnings.warn(\"Parcel() is deprecated\", DeprecationWarning, stacklevel=2)\n\n        self.service = service\n        self.country = country\n        self.city = city\n        self.postcode = postcode\n\n\nclass Destination(object):\n    \"\"\"Parcel destination data.\"\"\"\n\n    def __init__(self, country='DE', postcode=None, city=None, service='101'):\n        self.service = service\n        self.country = country\n        self.city = city\n        self.postcode = postcode\n\n\nclass Route:\n    \"\"\"Output of the routing algorithm.\"\"\"\n\n    def __init__(self, d_depot, o_sort, d_sort, grouping_priority, barcode_id,\n                 iata_code, service_text, service_mark, country, serviceinfo, countrynum,\n                 routingtable_version, postcode):\n        self.d_depot = d_depot\n        self.o_sort = o_sort\n        self.d_sort = d_sort\n        self.grouping_priority = grouping_priority\n        self.barcode_id = barcode_id\n        self.iata_code = iata_code\n        self.service_text = service_text\n        self.service_mark = service_mark\n        self.country = country\n        self.serviceinfo = serviceinfo\n        self.countrynum = countrynum\n        self.routingtable_version = routingtable_version\n        self.postcode = postcode\n\n    def __unicode__(self):\n        output = u\"\"\"Output parameters:\nCountry: %s\nD-Depot: %s\nO-Sort: %s\nD-Sort: %s\nGrouping priority: %s\nBarcode ID: %s\nITA Code: %s\nService Text: %s\"\"\" % (self.country, self.d_depot, self.o_sort, self.d_sort,\n                       self.grouping_priority, self.barcode_id, self.iata_code,\n                       self.service_text)\n        if self.service_mark:\n            output += \"\\nService Mark: %s\" % self.service_mark\n        if self.iata_code:\n            output += \"\\nIATA Code: %s\" % self.iata_code\n        if self.serviceinfo:\n            output += \"\\nService Info: %s\" % self.serviceinfo\n\n        return output\n\n    def __repr__(self):\n        return repr(vars(self))\n\n    def routingdata(self):\n        return {'o_sort': self.o_sort, 'd_sort': self.d_sort,\n                'd_depot': self.d_depot, 'country': self.country,\n                'service_text': self.service_text, 'serviceinfo': self.serviceinfo}\n\n\ndef _readfile(filename):\n    \"\"\"Read file line-by-line skipping comments.\"\"\"\n    if os.path.exists(filename + '.gz'):\n        fhandle = gzip.GzipFile(filename + '.gz')\n    else:\n        fhandle = file(filename)\n    for line in fhandle:\n        line = line.strip().decode('latin1')\n        if line.startswith('#'):\n            continue\n        yield line.split('|')\n\n\nclass RouteData(object):\n    \"\"\"More convenient representation of the georoute data.\"\"\"\n\n    def __init__(self, routingdepot='0142'):\n        \"\"\"Routingdepot the depot from where you are sending.\"\"\"\n        self.routingdepot = routingdepot\n        self.routingdepotgroups = ''\n        self.routingdepotcountry = ''\n\n        services = os.path.join(ROUTETABLES_BASE, 'SERVICE')\n        self.version = None\n        for line in file(services):\n            if line.startswith('#Version: '):\n                self.version = line.split(':')[1].strip()\n                break\n        if self.version is None:\n            raise InvalidFormatError(\"There's no version in the SERVICE file\")\n\n        self.countries = {}\n        for line in _readfile(os.path.join(ROUTETABLES_BASE, 'COUNTRY')):\n            isonum, isoname = line[:2]\n            self.countries[isoname.upper()] = isonum\n\n        self.depots = {}\n        for line in _readfile(os.path.join(ROUTETABLES_BASE, 'DEPOTS')):\n            geopostdepotnumber = line[0]\n            self.depots[geopostdepotnumber] = tuple(line)\n\n        self.services = {}\n        for line in _readfile(os.path.join(ROUTETABLES_BASE, 'SERVICE')):\n            servicecode = line[0]\n            self.services[servicecode] = tuple(line)\n\n        self.serviceinfo = {}\n        for line in _readfile(os.path.join(ROUTETABLES_BASE, 'SERVICEINFO.DE')):\n            servicecode = line[0]\n            self.serviceinfo[servicecode] = line[1]\n\n        filename = ROUTES_DB_BASE + ('-%s-%s.db' % (routingdepot, self.version))\n        self.db = sqlite3.connect(filename)\n\n        self.read_depots(ROUTETABLES_BASE)\n        self.read_locations(ROUTETABLES_BASE)\n        self.read_routes(ROUTETABLES_BASE)\n\n    def read_depots(self, path):\n        \"\"\"Read DEPOTS file and save all the information in a\n        SQLite database.\"\"\"\n        c = self.db.cursor()\n\n        c.execute(\"\"\"SELECT COUNT(*)\n                     FROM sqlite_master\n                     WHERE type = 'table' AND name = 'depots'\"\"\")\n        if not c.fetchone()[0]:\n            logging.info(\"regenerating depots table\")\n            c.execute(\"\"\"CREATE TABLE depots\n            (DepotNumber TEXT PRIMARY KEY,\n             IATACode TEXT,\n             GroupId TEXT,\n             Name1 TEXT,\n             Name2 TEXT,\n             Address1 TEXT,\n             Address2 TEXT,\n             Postcode TEXT,\n             CityName TEXT,\n             Country TEXT,\n             Phone TEXT,\n             Fax TEXT,\n             Mail TEXT,\n             Web TEXT)\"\"\")\n\n            for line in _readfile(os.path.join(path, 'DEPOTS')):\n                c.execute(\"\"\"INSERT INTO depots\n                             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\"\"\",\n                          line[:14])\n                if line[0] == self.routingdepot:\n                    self.routingdepotgroups = line[2]\n                    self.routingdepotgrouplist = line[2].split(',')\n                    self.routingdepotcountry = line[9]\n            c.execute('VACUUM;')\n\n    def read_locations(self, path):\n        \"\"\"Read LOCATION file and save all the information in a SQLite database.\"\"\"\n        c = self.db.cursor()\n\n        c.execute(\"\"\"SELECT COUNT(*)\n                     FROM sqlite_master\n                     WHERE type='table' AND name='location'\"\"\")\n        if not c.fetchone()[0]:\n            logging.info(\"regenerating location table\")\n            c.execute(\"\"\"CREATE TABLE location\n            (Area TEXT,\n             City TEXT,\n             Country TEXT,\n             Postcode TEXT)\"\"\")\n\n            for line in _readfile(os.path.join(path, 'LOCATION.DE')):\n                c.execute('INSERT INTO location VALUES (?,?,?,?)',\n                          line[:4])\n            c.execute('VACUUM;')\n\n    def read_routes(self, path):\n        \"\"\"Read ROUTES file and save all the information in a SQLite database.\"\"\"\n        # self.db = sqlite3.connect(ROUTES_DB)\n        c = self.db.cursor()\n\n        c.execute(\"\"\"SELECT COUNT(*)\n                     FROM sqlite_master\n                     WHERE type='table' AND name='routes'\"\"\")\n        if not c.fetchone()[0]:\n            logging.info(\"regenerating routes table\")\n            c.execute(\"\"\"CREATE TABLE routes\n            (id INTEGER PRIMARY KEY,\n            DestinationCountry TEXT,\n            BeginPostCode TEXT,\n            EndPostCode TEXT,\n            ServiceCodes TEXT,\n            RoutingPlaces TEXT,\n            SendingDate TEXT,\n            OSort TEXT,\n            DDepot TEXT,\n            GroupingPriority TEXT,\n            DSort TEXT,\n            BarcodeID TEXT)\"\"\")\n\n            c.execute(\"\"\"SELECT COUNT(*)\n                         FROM sqlite_master\n                         WHERE type='table' AND name='routedepots'\"\"\")\n            if not c.fetchone()[0]:\n                logging.info(\"regenerating routedepots table\")\n                c.execute(\"\"\"CREATE TABLE routedepots\n                (route INTEGER,\n                 depot TEXT)\"\"\")\n\n            c.execute(\"PRAGMA synchronous=OFF;\")\n            c.execute(\"PRAGMA temp_store=MEMORY;\")\n            i = 1\n            for line in _readfile(os.path.join(path, 'ROUTES')):\n                services = self.expand_services(line[3])\n                c.execute('INSERT INTO routes VALUES (?,?,?,?,?,?,?,?,?,?,?,?)',\n                          [i] + line[:3] + [services] + line[4:-1])\n                self.expand_depots(i, line[4], c)\n                i += 1\n\n            c.execute(\"CREATE INDEX routes_DestinationCountry ON routes(DestinationCountry)\")\n            c.execute(\"CREATE INDEX routes_BeginPostCode ON routes(BeginPostCode)\")\n            c.execute(\"CREATE INDEX routes_EndPostCode ON routes(EndPostCode)\")\n            c.execute(\"CREATE INDEX routedepots_route ON routedepots(route)\")\n            c.execute(\"CREATE INDEX routedepots_depot ON routedepots(depot)\")\n            c.execute('VACUUM;')  # also commits the database\n\n    def expand_services(self, services):\n        \"\"\"Expand services list.\"\"\"\n        services_list = []\n        for service in services.split(','):\n            if len(service) > 4:\n                start = int(service[1:4])\n                end = int(service[4:])\n                for i in range(start, end + 1):\n                    services_list.append(unicode(i))\n            else:\n                services_list.append(service[1:])\n\n        return ','.join(services_list)\n\n    def expand_depots(self, route, depots, c):\n        \"\"\"Parse depots list and generate route->depots relationship.\"\"\"\n        # but only four \"our\" depot.\n        # if you change the self.routingdepot, you have to rebuild the database\n        if depots == '':\n            c.execute(\"\"\"INSERT INTO routedepots(route, depot) VALUES (?, ?)\"\"\", (route, depots))\n            return\n\n        for depot in depots.split(','):\n            if depot.startswith('C'):\n                if depot[1:] == self.routingdepotcountry:\n                    c.execute(\"\"\"INSERT INTO routedepots(route, depot) VALUES(?, ?)\"\"\", (route,\n                                                                                         self.routingdepot))\n            elif depot.startswith('D'):\n                if len(depot) > 5:\n                    start = int(depot[1:5])\n                    end = int(depot[5:])\n                    for i in range(start, end + 1):\n                        if (\"%04d\" % i) == self.routingdepot:\n                            c.execute(\"\"\"INSERT INTO routedepots(route, depot) VALUES(?, ?)\"\"\",\n                                      (route, self.routingdepot))\n                else:\n                    if depot[1:5] == self.routingdepot:\n                        c.execute(\"\"\"INSERT INTO routedepots(route, depot) VALUES(?, ?)\"\"\",\n                                  (route, depot[1:5]))\n            elif depot.startswith('G'):\n                if depot[1:] in self.routingdepotgroups:\n                    c.execute(\"INSERT INTO routedepots(route, depot) VALUES(?, ?)\",\n                              (route, self.routingdepot))\n            else:\n                raise InvalidFormatError(\"Unable to parse depot '%s'\" % depot)\n\n    def get_countrynum(self, isoname):\n        \"\"\"Return country ISO code.\"\"\"\n        if not isoname.upper() in self.countries:\n            raise CountryError(\"Country %s unknown\" % isoname)\n        return self.countries[isoname.upper()]\n\n    def get_depot(self, depotnumber):\n        \"\"\"Return depot.\"\"\"\n        if not depotnumber in self.depots:\n            raise DepotError(\"Depot %s unknown\" % depotnumber)\n        return self.depots[depotnumber]\n\n    def get_service(self, servicecode):\n        \"\"\"Return service.\"\"\"\n        if not servicecode in self.services:\n            raise ServiceError(\"Service %s unknown\" % servicecode)\n        return self.services[servicecode]\n\n    def get_servicetext(self, servicecode):\n        \"\"\"Return service info to be printed on label.\"\"\"\n        if not servicecode in self.serviceinfo:\n            return ''\n        return self.serviceinfo[servicecode]\n\n    def translate_location(self, city, country):\n        \"\"\"Return postcode for given city and country.\"\"\"\n        cur = self.db.cursor()\n        cur.execute(\"SELECT Postcode FROM location WHERE City=? AND Country=?\", (city, country))\n        rows = cur.fetchall()\n        if not rows:\n            raise TranslationError(\"Cannot find postcode for location %s, %s\" % (city, country))\n        return rows[0][0]\n\n\nclass Router(object):\n    \"\"\"Routes parcels.\"\"\"\n\n    def __init__(self, data):\n        self.route_data = data\n        self.db = self.route_data.db\n\n    def route(self, parcel):\n        \"\"\"Find route.\"\"\"\n\n        self.current_subset = []\n        self.conditions = ['1=1']\n        self.cleanup_postcode(parcel)\n\n        self.select_country(parcel)\n        if parcel.postcode is None:\n            parcel.postcode = self.route_data.translate_location(parcel.city, parcel.country)\n            self.subsetstack.append(self.subset)\n\n        self.select_postcode(parcel)\n        self.select_service(parcel)\n        self.select_depot(parcel)\n        # Sending date is not used yet, according to documentation\n\n        # If there are several routes, always use the first one.\n        # In prior versions, an exception was raised instead.\n        if len(self.current_subset) >= 1:\n            rows = self.select_routes(\"1=1\")\n            (service_text, service_mark) = self.route_data.get_service(parcel.service)[1:3]\n            depot = self.route_data.get_depot(rows[0][8])\n            iata_code = depot[1]\n            country = depot[9]\n            if not country:\n                country = parcel.country\n            serviceinfo = self.route_data.get_servicetext(parcel.service)\n\n            return Route(rows[0][8], rows[0][7], rows[0][10], rows[0][9], rows[0][11],\n                         iata_code, service_text, service_mark, country,\n                         serviceinfo, self.route_data.get_countrynum(country),\n                         self.route_data.version, parcel.postcode)\n        raise NoRouteError(\"No route found for %r|%r|%r\" % \\\n              (parcel.country, parcel.postcode, parcel.service))\n\n    def add_condition(self, condition):\n        self.conditions.append(condition)\n\n    def select_routes(self, condition, params=()):\n        \"\"\"Find routes matching condition and currently selected subset of the routes table.\n        If routes are found, save their ids for narrowing future searches.\n        If no routes are found, do not change current subset.\n        \"\"\"\n\n        subsetcondition = ' AND '.join(self.conditions)\n        cur = self.db.cursor()\n        cur.execute(\"SELECT * FROM routes WHERE %s AND %s\" % (subsetcondition, condition), params)\n        rows = cur.fetchall()\n\n        # Save matched rows if there were any results\n        if rows:\n            self.add_condition(condition)\n        self.current_subset = [unicode(row[0]) for row in rows]\n        return rows\n\n    def select_country(self, parcel):\n        \"\"\"Select all routes with the given country.\"\"\"\n        rows = self.select_routes(\"DestinationCountry='%s'\" % (parcel.country.upper().replace(\"'\", ''), ))\n        if not rows:\n            raise CountryError(\"Country %s unknown\" % parcel.country)\n\n    def cleanup_postcode(self, parcel):\n        \"\"\"Removes spaces and country prefixes from postcodes.\"\"\"\n\n        if not parcel.postcode:\n            return\n        parcel.postcode = parcel.postcode.replace(' ', '').strip()\n        if parcel.postcode.startswith('-'):\n            parcel.postcode = parcel.postcode[1:]\n            self.cleanup_postcode(parcel)\n        if parcel.postcode.upper().startswith(parcel.country.upper()):\n            parcel.postcode = parcel.postcode[len(parcel.country):]\n            self.cleanup_postcode(parcel)\n        if parcel.country.upper() in ISO2CAR:\n            if parcel.postcode.upper().startswith(ISO2CAR[parcel.country.upper()]):\n                parcel.postcode = parcel.postcode[len(ISO2CAR[parcel.country.upper()]):]\n                self.cleanup_postcode(parcel)\n        if parcel.country.upper() == 'DE' and parcel.postcode.upper().startswith('CH-'):\n            parcel.country = 'CH'\n            parcel.postcode = parcel.postcode[2:]\n            self.cleanup_postcode(parcel)\n        if parcel.country.upper() == 'DE' and parcel.postcode.upper().startswith('BE-'):\n            parcel.country = 'BE'\n            parcel.postcode = parcel.postcode[2:]\n            self.cleanup_postcode(parcel)\n        if parcel.country.upper() == 'DE' and parcel.postcode.upper().startswith('B-'):\n            parcel.country = 'BE'\n            parcel.postcode = parcel.postcode[1:]\n            self.cleanup_postcode(parcel)\n        if parcel.country.upper() == 'DE' and parcel.postcode.upper().startswith('AT-'):\n            parcel.country = 'AT'\n            parcel.postcode = parcel.postcode[2:]\n            self.cleanup_postcode(parcel)\n        if parcel.country.upper() == 'DE' and parcel.postcode.upper().startswith('A-'):\n            parcel.country = 'AT'\n            parcel.postcode = parcel.postcode[1:]\n            self.cleanup_postcode(parcel)\n\n    def select_postcode(self, parcel):\n        \"\"\"Select all routes matching the given postcode.\"\"\"\n\n        # direct match\n        rows = self.select_routes(\"BeginPostCode='%s'\" % parcel.postcode.replace(\"'\", ''))\n        if not rows:\n            # range\n            rows = self.select_routes(\"BeginPostCode<='%s' AND EndPostCode>='%s'\"\n                                       % (parcel.postcode.replace(\"'\", ''), parcel.postcode.replace(\"'\", '')))\n        if not rows:\n            # catch all\n            rows = self.select_routes(\"BeginPostCode=''\")\n        if not rows:\n            raise NoRouteError(\"Postcode %r|%r unknown\" % (parcel.country, parcel.postcode))\n\n    def select_service(self, parcel):\n        \"\"\"Select all routes with the given service code.\"\"\"\n\n        # we have to redo postcode query as a backoff strategy\n        self.conditions.pop()\n        postcodequeries = [\"BeginPostCode='%s'\" % parcel.postcode.replace(\"'\", ''),\n            \"BeginPostCode<='%s' AND EndPostCode>='%s'\" % (parcel.postcode.replace(\"'\", ''),\n                                                           parcel.postcode.replace(\"'\", '')),\n            \"BeginPostCode=''\"]\n        for postcodequery in postcodequeries:\n            rows = self.select_routes(\"%s AND ServiceCodes LIKE '%%%s%%'\" % (postcodequery, parcel.service))\n            if not rows:\n                # catch all\n                rows = self.select_routes(\"%s AND ServiceCodes = ''\" % (postcodequery))\n            if rows:\n                break\n        if not rows:\n            raise ServiceError(\"No route for service found %r|%r|%r unknown\" % \\\n                (parcel.country, parcel.postcode, parcel.service))\n\n    def select_depot(self, parcel):\n        \"\"\"Select all routes with the given depot.\"\"\"\n        subset = \"route IN (%s)\" % ','.join([unicode(route) for route in self.current_subset])\n        cur = self.db.cursor()\n        cur.execute(\"SELECT route FROM routedepots WHERE depot=%s AND %s\" % (self.route_data.routingdepot,\n                                                                             subset))\n        rows = cur.fetchall()\n        if not rows:\n            cur.execute(\"SELECT route FROM routedepots WHERE %s\" % (subset))\n            rows = cur.fetchall()\n        if not rows:\n            raise RoutingDepotError(\"No route found for %r|%r|%r|%r|%r\" % \\\n                  (parcel.country, parcel.postcode, parcel.service, self.route_data.routingdepot, subset))\n        self.current_subset = [unicode(row[0]) for row in rows]\n\n\ndef get_route_without_cache(country=None, postcode=None, city=None, servicecode='101'):\n    router = Router(RouteData())\n    return router.route(Destination(country, postcode, city))\n\n\ndef get_route(country=None, postcode=None, city=None, servicecode='101'):\n    # this includes somewhat overly complex caching\n    filename = ROUTES_DB_BASE + ('_cache.db')\n    cache_db = sqlite3.connect(filename, isolation_level=None)\n    cur = cache_db.cursor()\n    # ensure table exists\n    cur.execute(\"\"\"SELECT COUNT(*)\n                 FROM sqlite_master\n                 WHERE type = 'table' AND name = 'routing_cache'\"\"\")\n    if not cur.fetchone()[0]:\n        logging.info(\"regenerating cache table\")\n        cur.execute(\"\"\"CREATE TABLE routing_cache\n        (country_postcode_servicecode TEXT PRIMARY KEY,\n        d_depot TEXT,\n        o_sort TEXT,\n        d_sort TEXT,\n        grouping_priority TEXT,\n        barcode_id TEXT,\n        iata_code TEXT,\n        service_text TEXT,\n        service_mark TEXT,\n        country TEXT,\n        serviceinfo TEXT,\n        countrynum TEXT,\n        routingtable_version TEXT,\n        postcode TEXT\n        )\"\"\")\n\n    # check if entry is cached\n    cur.execute(\"SELECT * FROM routing_cache WHERE country_postcode_servicecode='%s'\"\n                 % (\"%s_%s_%s\" % (country, postcode, servicecode)))\n    rows = cur.fetchall()\n    if not rows:\n        # nothing found\n        route = get_route_without_cache(country, postcode, city, servicecode)\n        cur.execute(\"\"\"INSERT INTO routing_cache\n                             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\"\"\",\n                             (\"%s_%s_%s\" % (country, postcode, servicecode),\n                             route.d_depot,\n                             route.o_sort,\n                             route.d_sort,\n                             route.grouping_priority,\n                             route.barcode_id,\n                             route.iata_code,\n                             route.service_text,\n                             route.service_mark,\n                             route.country,\n                             route.serviceinfo,\n                             route.countrynum,\n                             route.routingtable_version,\n                             route.postcode))\n        # For some reason closing the cache generated occasionally runtime errors.\n        # cache_db.close()\n    else:\n        if len(rows) > 1:\n            raise RuntimeError(\"to many cache hits!\")\n        route = Route(*rows[0][1:])\n    return route\n\n\n# compability layer to old georoute code prior to huLOG revision 1710\n\ndef find_route(depot, servicecode, land, plz):\n    \"\"\"Legacy method - to be removed.\"\"\"\n\n    import warnings\n    warnings.warn(\"georoute.find_route() is deprecated use get_route() instead\",\n                  DeprecationWarning, stacklevel=2)\n\n    if unicode(depot) != '0142':\n        raise RuntimeError(\"wrong depot\")\n    return get_route(unicode(land), unicode(plz), servicecode=unicode(servicecode))\n"
  },
  {
    "path": "pyshipping/carriers/dpd/georoute_test.py",
    "content": "#!/usr/bin/env python\n# -*- encoding: utf-8 -*-\n\n\"\"\"Test routing resolver for DPD. Coded by jmv, extended by md\"\"\"\n\nimport time\nimport unittest\nfrom pyshipping.carriers.dpd.georoute import get_route, get_route_without_cache\nfrom pyshipping.carriers.dpd.georoute import RouteData, Router, Destination\nfrom pyshipping.carriers.dpd.georoute import ServiceError, CountryError, TranslationError\n\n\nclass TestCase(unittest.TestCase):\n    \"\"\"Provide sophisticated dictionary comparision.\"\"\"\n\n    def assertDicEq(self, dict1, dict2):\n        \"\"\"Asserts if two dicts are unequal.\n\n        Raise an Exception which mentions the different entries of those dicts.\n        \"\"\"\n        if dict1 != dict2:\n            difference = set()\n            for key, value in dict1.items():\n                if dict2.get(key) != value:\n                    difference.add((key, value, dict2.get(key)))\n            for key, value in dict2.items():\n                if dict1.get(key) != value:\n                    difference.add((key, dict1.get(key), value))\n            raise self.failureException, \\\n                ('%r != %r: %s' % (dict1, dict2, list(difference)))\n\n\nclass RouteDataTest(TestCase):\n\n    def setUp(self):\n        self.data = RouteData()\n        self.db = self.data.db\n\n    def test_version(self):\n        self.assertEqual(self.data.version, '20110905')\n\n    def test_get_country(self):\n        self.assertRaises(CountryError, self.data.get_countrynum, 'URW')\n        self.assertEqual(self.data.get_countrynum('JP'), '392')\n        self.assertEqual(self.data.get_countrynum('DE'), '276')\n        self.assertEqual(self.data.get_countrynum('de'), '276')\n\n    def test_read_depots(self):\n        c = self.db.cursor()\n        c.execute(\"\"\"SELECT * FROM depots WHERE DepotNumber=?\"\"\", ('0015', ))\n        rows = c.fetchall()\n        self.assertEqual(1, len(rows))\n        self.assertEqual((u'0015', u'', u'', u'Betriebsgesellschaft DPD GmbH',\n                          u'', u'Otto-Hahn-Strasse 5', u'', u'59423', u'Unna', u'DE',\n                          u'+49-(0) 23 03-8 88-0', u'+49-(0) 23 03-8 88-31', u'', u''),\n                         rows[0])\n\n    def test_expand_depots(self):\n        c = self.db.cursor()\n        c.execute(\"\"\"SELECT id\n                     FROM routes\n                     WHERE DestinationCountry='DE' AND BeginPostCode='42477'\"\"\")\n        rows = c.fetchall()\n        # Interestingly we sometimes get two routes\n        # self.assertEqual(1, len(rows))\n        route = rows[0][0]\n        c.execute(\"SELECT depot FROM routedepots WHERE route=?\", (route, ))\n        rows = c.fetchall()\n        self.assertEqual(1, len(rows))\n\n    def test_get_service(self):\n        self.assertEqual(self.data.get_service('180'), ('180', 'AM1-NO', '', '022,160', ''))\n        self.assertRaises(ServiceError, self.data.get_service, '100000')\n\n    def test_get_servicetext(self):\n        text = self.data.get_servicetext('185')\n        self.assertEqual('DPD 10:00 Unfrei / ex works', text)\n\n    def test_translate_location(self):\n        self.assertEqual('1', self.data.translate_location('Dublin', 'IE'))\n        self.assertRaises(TranslationError, self.data.translate_location, 'Cahir', 'IE')\n\n\nclass RouterTest(TestCase):\n\n    def setUp(self):\n        self.data = RouteData()\n        self.router = Router(self.data)\n\n    def test_known_routes_de(self):\n        route = self.router.route(Destination(postcode='42477'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0142', 'serviceinfo': '', 'country': u'DE',\n                                               'd_sort': u'65', 'o_sort': u'42', 'service_text': u'D'})\n        route = self.router.route(Destination(postcode='42897'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0142', 'serviceinfo': '', 'country': 'DE',\n                                               'd_sort': '15', 'o_sort': '42', 'service_text': 'D'})\n        route = self.router.route(Destination(postcode='53111'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0150', 'serviceinfo': '', 'country': 'DE',\n                                               'd_sort': u'205', 'o_sort': '50', 'service_text': 'D'})\n        route = self.router.route(Destination(postcode='53111', country='DE'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0150', 'serviceinfo': '', 'country': 'DE',\n                                               'd_sort': u'205', 'o_sort': '50', 'service_text': 'D'})\n        route = self.router.route(Destination('DE', '53111'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0150', 'serviceinfo': '', 'country': 'DE',\n                                               'd_sort': u'205', 'o_sort': '50', 'service_text': 'D'})\n        route = self.router.route(Destination('DE', '53111', city='Bonn'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0150', 'serviceinfo': '', 'country': 'DE',\n                                               'd_sort': u'205', 'o_sort': '50', 'service_text': 'D'})\n        route = self.router.route(Destination('DE', '53111', 'Bonn'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0150', 'serviceinfo': '', 'country': 'DE',\n                                               'd_sort': u'205', 'o_sort': '50', 'service_text': 'D'})\n\n    def test_known_routes_world(self):\n        route = self.router.route(Destination(postcode='66400', country='FR'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0470', 'serviceinfo': '', 'country': u'FR',\n                                               'd_sort': u'U50', 'o_sort': u'16', 'service_text': u'D'})\n        route = self.router.route(Destination('FR', '66400', 'Ceret'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0470', 'serviceinfo': '', 'country': 'FR',\n                                               'd_sort': 'U50', 'o_sort': '16', 'service_text': 'D'})\n        route = self.router.route(Destination('BE', '3960', 'Bree/Belgien'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0532', 'serviceinfo': '', 'country': 'BE',\n                                               'd_sort': u'A353', 'o_sort': '52', 'service_text': 'D'})\n        route = self.router.route(Destination('CH', '6005', 'Luzern'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0616', 'serviceinfo': '', 'country': 'CH',\n                                               'd_sort': '40', 'o_sort': '78', 'service_text': 'D'})\n        route = self.router.route(Destination('AT', '1210', 'Wien'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0622', 'serviceinfo': '', 'country': 'AT',\n                                               'd_sort': '10', 'o_sort': '62', 'service_text': 'D'})\n        route = self.router.route(Destination('AT', '4820', 'Bad Ischl'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0624', 'serviceinfo': '', 'country': 'AT',\n                                               'd_sort': '63', 'o_sort': '62', 'service_text': 'D'})\n        route = self.router.route(Destination('AT', '7400', 'Oberwart'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0628', 'serviceinfo': '', 'country': 'AT',\n                                               'd_sort': u'3270', 'o_sort': '62', 'service_text': 'D'})\n        route = self.router.route(Destination('AT', '4400', 'Steyr'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0624', 'serviceinfo': '', 'country': 'AT',\n                                               'd_sort': '70', 'o_sort': '62', 'service_text': 'D'})\n        route = self.router.route(Destination('AT', '1220', 'Wien'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0622', 'serviceinfo': '', 'country': 'AT',\n                                               'd_sort': '30', 'o_sort': '62', 'service_text': 'D'})\n        route = self.router.route(Destination('AT', '6890', 'Lustenau'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0627', 'serviceinfo': '', 'country': 'AT',\n                                               'd_sort': '01', 'o_sort': '62', 'service_text': 'D'})\n        route = self.router.route(Destination('BE', '3520', 'ZONHOVEN'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0532', 'serviceinfo': '', 'country': u'BE',\n                                               'd_sort': u'A369', 'o_sort': u'52', 'service_text': u'D'})\n        route = self.router.route(Destination('BE', '4890', 'Thimister'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0532', 'serviceinfo': '', 'country': 'BE',\n                                               'd_sort': u'B326', 'o_sort': '52', 'service_text': 'D'})\n        route = self.router.route(Destination('CH', '8305', 'Dietlikon'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0615', 'serviceinfo': '', 'country': 'CH',\n                                               'd_sort': '77', 'o_sort': '78', 'service_text': 'D'})\n        route = self.router.route(Destination('CH', '4051', 'Basel'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0610', 'serviceinfo': '', 'country': 'CH',\n                                               'd_sort': u'16', 'o_sort': '78', 'service_text': 'D'})\n        route = self.router.route(Destination('CH', '8808', 'Pf<C3><A4>ffikon'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0615', 'serviceinfo': '', 'country': 'CH',\n                                               'd_sort': '71', 'o_sort': '78', 'service_text': 'D'})\n        route = self.router.route(Destination('DK', '9500', 'Hobro'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0504', 'serviceinfo': '', 'country': 'DK',\n                                               'd_sort': u'405', 'o_sort': '20', 'service_text': 'D'})\n        # Lichtenstein is routed via CH\n        route = self.router.route(Destination('LI', '8399', 'Windhof / Luxembourg'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0617', 'serviceinfo': '', 'country': 'CH',\n                                               'd_sort': '', 'o_sort': '78', 'service_text': 'D'})\n        route = self.router.route(Destination('LI', '9495', 'Triesen'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0617', 'serviceinfo': '', 'country': 'CH',\n                                               'd_sort': '', 'o_sort': '78', 'service_text': 'D'})\n        route = self.router.route(Destination('LI', '8440', 'Steinfort'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0617', 'serviceinfo': '', 'country': 'CH',\n                                               'd_sort': '', 'o_sort': '78', 'service_text': 'D'})\n        route = self.router.route(Destination('CZ', '41742', 'Krupka 1'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '1380', 'serviceinfo': '', 'country': 'CZ',\n                                               'd_sort': '21', 'o_sort': '10', 'service_text': 'D'})\n        route = self.router.route(Destination('ES', '28802', 'Alcala de Henares (Madrid)'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0728', 'serviceinfo': '', 'country': 'ES',\n                                               'd_sort': '01', 'o_sort': '16', 'service_text': 'D'})\n        route = self.router.route(Destination('ES', '28010', 'Madrid'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0728', 'serviceinfo': '', 'country': 'ES',\n                                               'd_sort': '01', 'o_sort': '16', 'service_text': 'D'})\n        route = self.router.route(Destination('FR', '84170', 'MONTEUX'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0447', 'serviceinfo': '', 'country': 'FR',\n                                               'd_sort': u'S65', 'o_sort': '16', 'service_text': 'D'})\n        route = self.router.route(Destination('FR', '91044', 'Evry Cedex'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0408', 'serviceinfo': '', 'country': u'FR',\n                                               'd_sort': u'S61', 'o_sort': u'50', 'service_text': u'D'})\n\n    def test_difficult_routingdepots(self):\n        route = self.router.route(Destination('AT', '3626', 'H<C3><BC>nibach'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0623', 'serviceinfo': u'', 'country': 'AT',\n                                               'd_sort': u'01', 'o_sort': u'62', 'service_text': u'D'})\n        route = self.router.route(Destination('AT', '8225', 'P<C3><B6>llau'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0628', 'serviceinfo': u'', 'country': 'AT',\n                                               'd_sort': u'1290', 'o_sort': u'62', 'service_text': u'D'})\n        route = self.router.route(Destination('AT', '5020', 'Salzburg'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0625', 'serviceinfo': u'', 'country': 'AT',\n                                               'd_sort': u'1000', 'o_sort': u'62', 'service_text': u'D'})\n        route = self.router.route(Destination('SE', '65224', 'Karlstad'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0307', 'serviceinfo': u'', 'country': 'SE',\n                                               'd_sort': u'01', 'o_sort': u'20', 'service_text': u'D'})\n        route = self.router.route(Destination('AT', '2734', 'Buchberg/Schneeberg'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0621', 'serviceinfo': u'', 'country': 'AT',\n                                               'd_sort': u'64', 'o_sort': u'62', 'service_text': u'D'})\n\n    def test_difficult_service(self):\n        route = self.router.route(Destination('AT', '4240', 'Freistadt Österreich'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0634', 'serviceinfo': '', 'country': u'AT',\n                                               'd_sort': u'22', 'o_sort': u'62', 'service_text': u'D'})\n        route = self.router.route(Destination('AT', '5101', 'Bergheim bei Salzburg'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0625', 'serviceinfo': u'', 'country': 'AT',\n                                               'd_sort': u'2509', 'o_sort': u'62', 'service_text': u'D'})\n        route = self.router.route(Destination('AT', '8230', 'Hartberg'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0628', 'serviceinfo': u'', 'country': 'AT',\n                                               'd_sort': u'1290', 'o_sort': u'62', 'service_text': u'D'})\n        route = self.router.route(Destination('AT', '8045', 'Graz/<C3><96>sterreich'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0628', 'serviceinfo': u'', 'country': 'AT',\n                                               'd_sort': u'2840', 'o_sort': u'62', 'service_text': u'D'})\n\n    def test_postcode_with_country(self):\n        route = self.router.route(Destination(postcode='FR-66400', country='FR'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0470', 'serviceinfo': '', 'country': u'FR',\n                                               'd_sort': u'U50', 'o_sort': u'16', 'service_text': u'D'})\n        route = self.router.route(Destination(postcode='FR 66400', country='FR'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0470', 'serviceinfo': '', 'country': u'FR',\n                                               'd_sort': u'U50', 'o_sort': u'16', 'service_text': u'D'})\n        route = self.router.route(Destination(postcode='FR66400', country='FR'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0470', 'serviceinfo': '', 'country': 'FR',\n                                               'd_sort': 'U50', 'o_sort': '16', 'service_text': 'D'})\n        route = self.router.route(Destination(postcode='F-66400', country='FR'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0470', 'serviceinfo': '', 'country': 'FR',\n                                               'd_sort': 'U50', 'o_sort': '16', 'service_text': 'D'})\n\n    def test_postcode_spaces(self):\n        route = self.router.route(Destination(postcode='42 477'))\n        self.assertDicEq(route.routingdata(), {'o_sort': '42', 'serviceinfo': '', 'country': 'DE',\n                                               'd_sort': '65', 'd_depot': '0142', 'service_text': 'D'})\n        route = self.router.route(Destination(postcode=' 42477'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0142', 'serviceinfo': '', 'country': u'DE',\n                                               'd_sort': u'65', 'o_sort': u'42', 'service_text': u'D'})\n        route = self.router.route(Destination(postcode=' 42477 '))\n        self.assertDicEq(route.routingdata(), {'d_depot': '0142', 'serviceinfo': '', 'country': 'DE',\n                                               'd_sort': '65', 'o_sort': '42', 'service_text': 'D'})\n        # real live sample\n        route = self.router.route(Destination('GB', 'GU148HN', 'Hampshire'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '1550', 'serviceinfo': '', 'country': 'GB',\n                                               'd_sort': '', 'o_sort': '52', 'service_text': 'D'})\n        route = self.router.route(Destination('GB', 'GU 14 8HN', 'Hampshire'))\n        self.assertDicEq(route.routingdata(), {'d_depot': '1550', 'serviceinfo': '', 'country': 'GB',\n                                               'd_sort': '', 'o_sort': '52', 'service_text': 'D'})\n\n    def test_problematic_routes(self):\n        # Lichtenstein is problematic because usually it is routed trough Swizerland.\n        route = self.router.route(Destination('LI', '8440'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0617', 'serviceinfo': '', 'country': u'CH',\n                                               'd_sort': u'', 'o_sort': u'78', 'service_text': u'D'})\n        route = self.router.route(Destination('LI', '8440', 'Steinfort'))\n        self.assertDicEq(route.routingdata(), {'d_depot': u'0617', 'serviceinfo': '', 'country': u'CH',\n                                               'd_sort': u'', 'o_sort': u'78', 'service_text': u'D'})\n\n    def test_international(self):\n        # AR   | 1426  | Buenos Aire\n        self.assertDicEq(get_route('AR', '1426').routingdata(),\n            {'d_depot': u'0920', 'serviceinfo': u'', 'country': u'AR', 'd_sort': u'', 'o_sort': u'16',\n             'service_text': u'D'})\n        # AZ   | 1073 | Baku\n        self.assertDicEq(get_route('AZ', '1073').routingdata(),\n            {'d_depot': u'0918', 'serviceinfo': '', 'country': 'AZ', 'd_sort': u'CDG', 'o_sort': u'16',\n             'service_text': u'D'})\n        # BE   | 3960 | Bree/Belgien\n        self.assertDicEq(get_route('BE', '3960').routingdata(),\n            {'d_depot': u'0532', 'serviceinfo': u'', 'country': u'BE', 'd_sort': u'A353', 'o_sort': u'52',\n            'service_text': u'D'})\n        # BG   | 1766 | Sofia\n        self.assertDicEq(get_route('BG', '1766').routingdata(),\n            {'d_depot': u'1660', 'serviceinfo': '', 'country': u'BG', 'd_sort': u'', 'o_sort': u'62', 'service_text': u'D'})\n        # CH   | 3601 | Thun/Schweiz\n        self.assertDicEq(get_route('CH', '3601').routingdata(),\n            {'d_depot': u'0612', 'serviceinfo': u'', 'country': u'CH', 'd_sort': u'', 'o_sort': u'78',\n            'service_text': u'D'})\n        # CZ   | 16300 | Praha 6- Repy\n        self.assertDicEq(get_route('CZ', '16300').routingdata(),\n            {'d_depot': u'1391', 'serviceinfo': u'', 'country': u'CZ', 'd_sort': u'B854', 'o_sort': u'10',\n            'service_text': u'D'})\n        # CZ   | 71000 | Ostrava\n        self.assertDicEq(get_route('CZ', '71000').routingdata(),\n            {'d_depot': u'1384', 'serviceinfo': u'', 'country': u'CZ', 'd_sort': u'412', 'o_sort': u'10',\n            'service_text': u'D'})\n        # DE   | 04316 | Leipzig\n        self.assertDicEq(get_route('DE', '04316').routingdata(),\n            {'d_depot': u'0104', 'serviceinfo': '', 'country': u'DE', 'd_sort': u'2CPO', 'o_sort': u'10',\n             'service_text': u'D'})\n        # DE   | 99974 | M<C3><BC>hlhausen / Th<C3><BC>ringen\n        self.assertDicEq(get_route('DE', '99974').routingdata(),\n            {'d_depot': u'0234', 'serviceinfo': u'', 'country': u'DE', 'd_sort': u'C015', 'o_sort': u'KK02',\n            'service_text': u'D'})\n        # DK   | 4000  | Roskilde\n        self.assertDicEq(get_route('DK', '4000').routingdata(),\n            {'d_depot': u'0500', 'serviceinfo': u'', 'country': u'DK', 'd_sort': u'01', 'o_sort': u'20',\n            'service_text': u'D'})\n        # DK   | 9500   | Hobro\n        self.assertDicEq(get_route('DK', '9500').routingdata(),\n            {'d_depot': u'0504', 'serviceinfo': u'', 'country': u'DK', 'd_sort': u'405', 'o_sort': u'20',\n            'service_text': u'D'})\n        # EE   | 10621  | Tallinn\n        self.assertDicEq(get_route('EE', '10621').routingdata(),\n            {'d_depot': u'0560', 'serviceinfo': u'', 'country': u'EE', 'd_sort': u'0005', 'o_sort': u'13',\n            'service_text': u'D'})\n        # ES   | 08227  | Terrassa\n        self.assertDicEq(get_route('ES', '08227').routingdata(),\n            {'d_depot': u'0708', 'serviceinfo': u'', 'country': u'ES', 'd_sort': u'01', 'o_sort': u'16',\n            'service_text': u'D'})\n        # FI   | 94700  | Kemi\n        self.assertDicEq(get_route('FI', '94700').routingdata(),\n            {'d_depot': u'1614', 'serviceinfo': u'', 'country': u'FI', 'd_sort': u'510', 'o_sort': u'15',\n            'service_text': u'D'})\n        # FR   | 91044  | EVRY-LISSES\n        self.assertDicEq(get_route('FR', '91044').routingdata(),\n            {'d_depot': u'0408', 'serviceinfo': '', 'country': u'FR', 'd_sort': u'S61', 'o_sort': u'50',\n             'service_text': u'D'})\n        # GB   | BT387AR              | Carrickfergus\n        self.assertDicEq(get_route('GB', 'BT387AR').routingdata(),\n            {'d_depot': u'1598', 'serviceinfo': u'', 'country': u'GB', 'd_sort': u'', 'o_sort': u'52',\n            'service_text': u'D'})\n        # GB   | CB13SW               | Cambridge\n        self.assertDicEq(get_route('GB', 'CB13SW').routingdata(),\n            {'d_depot': u'1550', 'serviceinfo': u'', 'country': u'GB', 'd_sort': u'', 'o_sort': u'52',\n            'service_text': u'D'})\n        # GB   | G43 2DX              | Glasgow\n        self.assertDicEq(get_route('GB', 'G43 2DX').routingdata(),\n            {'d_depot': u'1550', 'serviceinfo': u'', 'country': u'GB', 'd_sort': u'', 'o_sort': u'52',\n            'service_text': u'D'})\n        # GB   | G432DX               | Glasgow\n        self.assertDicEq(get_route('GB', 'G432DX').routingdata(),\n            {'d_depot': u'1550', 'serviceinfo': u'', 'country': u'GB', 'd_sort': u'', 'o_sort': u'52',\n            'service_text': u'D'})\n        # GB   | N41NR                | London\n        self.assertDicEq(get_route('GB', 'N41NR').routingdata(),\n            {'d_depot': u'1550', 'serviceinfo': u'', 'country': u'GB', 'd_sort': u'', 'o_sort': u'52',\n            'service_text': u'D'})\n        # GR   | 17341  | Athens\n        self.assertDicEq(get_route('GR', '17341').routingdata(),\n            {'d_depot': u'1251', 'serviceinfo': u'', 'country': u'GR', 'd_sort': u'', 'o_sort': u'62',\n            'service_text': u'D'})\n        # GR   | 64200  | Chrisoupoli-KAVALA\n        self.assertDicEq(get_route('GR', '64200').routingdata(),\n            {'d_depot': u'1251', 'serviceinfo': u'', 'country': u'GR', 'd_sort': u'', 'o_sort': u'62',\n            'service_text': u'D'})\n        # HR   | 10000 | Zagreb\n        self.assertDicEq(get_route('HR', '10000').routingdata(),\n            {'d_depot': u'1750', 'serviceinfo': u'', 'country': u'HR', 'd_sort': u'SVI', 'o_sort': u'62',\n            'service_text': u'D'})\n        # HR   | 44000 | Sisak\n        self.assertDicEq(get_route('HR', '44000').routingdata(),\n            {'d_depot': u'1750', 'serviceinfo': u'', 'country': u'HR', 'd_sort': u'020', 'o_sort': u'62',\n            'service_text': u'D'})\n        # HU   | 1121  | Budapest\n        self.assertDicEq(get_route('HU', '1121').routingdata(),\n            {'d_depot': u'1640', 'serviceinfo': u'', 'country': u'HU', 'd_sort': u'027', 'o_sort': u'62',\n            'service_text': u'D'})\n        # HU   | 9400  | Sopron\n        self.assertDicEq(get_route('HU', '9400').routingdata(),\n            {'d_depot': u'1657', 'serviceinfo': u'', 'country': u'HU', 'd_sort': u'684', 'o_sort': u'62',\n            'service_text': u'D'})\n        # IT   | 34100    | Trieste / Italien\n        self.assertDicEq(get_route('IT', '34100').routingdata(),\n            {'d_depot': u'0835', 'serviceinfo': u'', 'country': u'IT', 'd_sort': u'01', 'o_sort': u'16',\n            'service_text': u'D'})\n        # LI   | 09494    | Schaan\n        self.assertDicEq(get_route('LI', '09494').routingdata(),\n            {'d_depot': u'0617', 'serviceinfo': u'', 'country': u'CH', 'd_sort': u'', 'o_sort': u'78',\n            'service_text': u'D'})\n        # LI   | 9999     | Wemperhardt\n        self.assertDicEq(get_route('LI', '9999').routingdata(),\n            {'d_depot': u'0617', 'serviceinfo': u'', 'country': u'CH', 'd_sort': u'', 'o_sort': u'78',\n            'service_text': u'D'})\n        # LI   | CH-9491  | Ruggell\n        self.assertDicEq(get_route('LI', 'CH-9491').routingdata(),\n            {'d_depot': u'0617', 'serviceinfo': u'', 'country': u'CH', 'd_sort': u'', 'o_sort': u'78',\n            'service_text': u'D'})\n        # LI   | 9491  | Ruggell\n        self.assertDicEq(get_route('LI', '9491').routingdata(),\n            {'d_depot': u'0617', 'serviceinfo': u'', 'country': u'CH', 'd_sort': u'', 'o_sort': u'78',\n            'service_text': u'D'})\n        # LT   | 3031     | Kaunas\n        self.assertDicEq(get_route('LT', '3031').routingdata(),\n            {'d_depot': u'0594', 'serviceinfo': '', 'country': u'LT', 'd_sort': u'732', 'o_sort': u'13',\n             'service_text': u'D'})\n        # LV   | 1039     | Riga\n        self.assertDicEq(get_route('LV', '1039').routingdata(),\n            {'d_depot': u'0575', 'serviceinfo': u'', 'country': u'LV', 'd_sort': u'R39', 'o_sort': u'13',\n            'service_text': u'D'})\n        # NL   | 7443TC   | Nijverdal\n        self.assertDicEq(get_route('NL', '7443TC').routingdata(),\n            {'d_depot': u'0512', 'serviceinfo': '', 'country': u'NL', 'd_sort': u'B535', 'o_sort': u'52',\n             'service_text': u'D'})\n        # NL   | 9405 JB  | Assen\n        self.assertDicEq(get_route('NL', '9405 JB').routingdata(),\n            {'d_depot': u'0513', 'serviceinfo': u'', 'country': u'NL', 'd_sort': u'B241', 'o_sort': u'52',\n            'service_text': u'D'})\n        # NO   | 6800     | Förde\n        self.assertDicEq(get_route('NO', '6800').routingdata(),\n            {'d_depot': u'0360', 'serviceinfo': u'', 'country': u'NO', 'd_sort': u'01', 'o_sort': u'15',\n            'service_text': u'D'})\n        # PL   | 80516    | Gdansk\n        self.assertDicEq(get_route('PL', '80516').routingdata(),\n            {'d_depot': u'1306', 'serviceinfo': u'', 'country': u'PL', 'd_sort': u'', 'o_sort': u'13',\n            'service_text': u'D'})\n        # PL   | 22300    | Krasnystaw\n        self.assertDicEq(get_route('PL', '22300').routingdata(),\n            {'d_depot': u'1300', 'serviceinfo': u'', 'country': u'PL', 'd_sort': u'', 'o_sort': u'13',\n            'service_text': u'D'})\n        # SE   | 11358    | Stockholm\n        self.assertDicEq(get_route('SE', '11358').routingdata(),\n            {'d_depot': u'0312', 'serviceinfo': u'', 'country': u'SE', 'd_sort': u'01', 'o_sort': u'20',\n            'service_text': u'D'})\n        # SE   | 75752    | Uppsala\n        self.assertDicEq(get_route('SE', '75752').routingdata(),\n            {'d_depot': u'0312', 'serviceinfo': u'', 'country': u'SE', 'd_sort': u'01', 'o_sort': u'20',\n            'service_text': u'D'})\n        # SI   | 1225     | Lukovica\n        self.assertDicEq(get_route('SI', '1225').routingdata(),\n            {'d_depot': u'1696', 'serviceinfo': '', 'country': u'SI', 'd_sort': u'14', 'o_sort': u'62',\n             'service_text': u'D'})\n        # SK   | 82105    | Bratislava\n        self.assertDicEq(get_route('SK', '82105').routingdata(),\n            {'d_depot': u'0660', 'serviceinfo': '', 'country': u'SK', 'd_sort': u'10', 'o_sort': u'10',\n             'service_text': u'D'})\n\n    def test_incorrectCountry(self):\n        self.assertRaises(CountryError, get_route, 'URG', '42477')\n\n    def test_incorrectLocation(self):\n        self.assertRaises(TranslationError, get_route, 'DE', None)\n\n    def test_incorrectService(self):\n        self.assertRaises(ServiceError, get_route, 'DE', '0001')\n\n    def test_select_routes(self):\n        self.router.conditions = ['1=1']\n        rows = self.router.select_routes('DestinationCountry=?', ('UZ', ))\n        self.assert_(len(rows) > 0)\n\n    def test_cache(self):\n        self.assertDicEq(vars(get_route('LI', '8440')), vars(get_route_without_cache('LI', '8440')))\n\n\nclass HighLevelTest(TestCase):\n\n    def test_get_route(self):\n        self.assertDicEq(vars(get_route('DE', '42897')),\n            {'service_mark': u'', 'o_sort': u'42', 'serviceinfo': u'', 'barcode_id': u'37',\n             'grouping_priority': u'', 'country': u'DE', 'countrynum': u'276',\n             'routingtable_version': u'20110905', 'iata_code': u'', 'd_sort': u'15',\n             'postcode': u'42897', 'd_depot': u'0142', 'service_text': u'D'})\n        self.assertDicEq(vars(get_route('DE', '42897', 'Remscheid')),\n            {'service_mark': u'', 'o_sort': u'42', 'serviceinfo': u'', 'barcode_id': u'37',\n             'grouping_priority': u'', 'country': u'DE', 'countrynum': u'276',\n             'routingtable_version': u'20110905', 'iata_code': u'', 'd_sort': u'15',\n             'postcode': u'42897', 'd_depot': u'0142', 'service_text': u'D'})\n        self.assertDicEq(vars(get_route('DE', '42897', 'Remscheid', '101')),\n            {'service_mark': u'', 'o_sort': u'42', 'serviceinfo': u'', 'barcode_id': u'37',\n             'grouping_priority': u'', 'country': u'DE', 'countrynum': u'276',\n             'routingtable_version': u'20110905', 'iata_code': u'', 'd_sort': u'15',\n             'postcode': u'42897', 'd_depot': u'0142', 'service_text': u'D'})\n        self.assertDicEq(vars(get_route('LI', '8440')),\n            {'service_mark': u'', 'o_sort': u'78', 'serviceinfo': u'', 'barcode_id': u'37',\n             'grouping_priority': u'', 'country': u'CH', 'countrynum': u'756',\n             'routingtable_version': u'20110905', 'iata_code': u'', 'd_sort': u'',\n             'postcode': u'8440', 'd_depot': u'0617', 'service_text': u'D'})\n        self.assertDicEq(vars(get_route(u'LI', '8440')),\n            {'service_mark': u'', 'o_sort': u'78', 'serviceinfo': u'', 'barcode_id': u'37',\n             'grouping_priority': u'', 'country': u'CH', 'countrynum': u'756',\n             'routingtable_version': u'20110905', 'iata_code': u'', 'd_sort': u'',\n             'postcode': u'8440', 'd_depot': u'0617', 'service_text': u'D'})\n        self.assertDicEq(vars(get_route(u'LI', u'8440')),\n            {'service_mark': u'', 'o_sort': u'78', 'serviceinfo': u'', 'barcode_id': u'37',\n             'grouping_priority': u'', 'country': u'CH', 'countrynum': u'756',\n             'routingtable_version': u'20110905', 'iata_code': u'', 'd_sort': u'',\n             'postcode': u'8440', 'd_depot': u'0617', 'service_text': u'D'})\n\n    def test_cache(self):\n        self.assertEqual(vars(get_route('LI', '8440')), vars(get_route_without_cache('LI', '8440')))\n\nif __name__ == '__main__':\n    start = time.time()\n    router = Router(RouteData())\n    stamp = time.time()\n    router.route(Destination('AT', '4240', 'Freistadt Österreich')).routingdata()\n    end = time.time()\n    # print (\"took %.3fs to find a single route (including %.3fs initialisation overhead)\"\n    #        % (end-start, stamp-start))\n\n    unittest.main()\n"
  },
  {
    "path": "pyshipping/carriers/dpd/georoutetables/COUNTRY",
    "content": "#Filename: COUNTRY\n#Version: 20140505\n#Expiration: 20140831\n#Hash: 01f9fd1927280385e2afd8287ca19c8528cb4ad2\n#Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt\n#Fields: ISO-NumCountryCode|ISO-Alpha2CountryCode|ISO-Alpha3CountryCode|DestinationLanguages|FlagPostCodeNo|\n#Key: ISO-NumCountryCode|\n004|AF|AFG|EN|0|\n008|AL|ALB|SQ|0|\n010|AQ|ATA|EN|1|\n012|DZ|DZA|FR|0|\n016|AS|ASM|EN|0|\n020|AD|AND|FR,ES,CA|0|\n024|AO|AGO|PT|1|\n028|AG|ATG|EN|1|\n031|AZ|AZE|AZ|0|\n032|AR|ARG|ES|0|\n036|AU|AUS|EN|0|\n040|AT|AUT|DE|0|\n044|BS|BHS|EN|1|\n048|BH|BHR|AR|0|\n050|BD|BGD|BN,EN|0|\n051|AM|ARM|HY|0|\n052|BB|BRB|EN|1|\n056|BE|BEL|FR,NL,DE|0|\n060|BM|BMU|EN|0|\n064|BT|BTN|DZ|0|\n068|BO|BOL|ES,AY,QU|1|\n070|BA|BIH|BS,HR,SR|0|\n072|BW|BWA|EN|0|\n074|BV|BVT|NO,EN|1|\n076|BR|BRA|PT|0|\n084|BZ|BLZ|EN|0|\n086|IO|IOT|EN|0|\n090|SB|SLB|EN|1|\n092|VG|VGB|EN|0|\n096|BN|BRN|MS,EN|0|\n100|BG|BGR|BG|0|\n104|MM|MMR|MY|0|\n108|BI|BDI|FR,RN|1|\n112|BY|BLR|BE,RU|0|\n116|KH|KHM|KM|0|\n120|CM|CMR|FR,EN|0|\n124|CA|CAN|EN,FR|0|\n132|CV|CPV|PT|0|\n136|KY|CYM|EN|0|\n140|CF|CAF|FR,SG|0|\n144|LK|LKA|SI,TA,EN|0|\n148|TD|TCD|FR,AR|1|\n152|CL|CHL|ES|0|\n156|CN|CHN|ZH|0|\n158|TW|TWN|EN|0|\n162|CX|CXR|EN|1|\n166|CC|CCK|EN|0|\n170|CO|COL|ES|1|\n174|KM|COM|FR,AR|0|\n175|YT|MYT|FR,MG,SW|0|\n178|CG|COG|FR,LN|0|\n180|CD|COD|FR|0|\n184|CK|COK|EN|0|\n188|CR|CRI|ES|0|\n191|HR|HRV|HR|0|\n192|CU|CUB|ES|0|\n196|CY|CYP|EL,TR|0|\n203|CZ|CZE|CS|0|\n204|BJ|BEN|FR|0|\n208|DK|DNK|DA|0|\n212|DM|DMA|EN|0|\n214|DO|DOM|ES|0|\n218|EC|ECU|ES,QU|0|\n222|SV|SLV|ES|1|\n226|GQ|GNQ|ES,FR|1|\n231|ET|ETH|AM|0|\n232|ER|ERI|AR,TI,EN|0|\n233|EE|EST|ET|0|\n234|FO|FRO|FO|0|\n238|FK|FLK|EN|0|\n239|GS|SGS|EN|0|\n242|FJ|FJI|EN|0|\n246|FI|FIN|FI,SV|0|\n248|AX|ALA|SV|0|\n250|FR|FRA|FR|0|\n254|GF|GUF|FR,EN|0|\n258|PF|PYF|FR,TY|0|\n260|TF|ATF|FR,EN|1|\n262|DJ|DJI|FR,AR,AA|1|\n266|GA|GAB|FR|0|\n268|GE|GEO|KA|0|\n270|GM|GMB|EN|1|\n275|PS|PSE|AR,EN|0|\n276|DE|DEU|DE|0|\n288|GH|GHA|EN|1|\n292|GI|GIB|ES,EN|1|\n296|KI|KIR|EN|1|\n300|GR|GRC|EL,EN|0|\n304|GL|GRL|DA|0|\n308|GD|GRD|EN|1|\n312|GP|GLP|FR|0|\n316|GU|GUM|EN|0|\n320|GT|GTM|ES|0|\n324|GN|GIN|FR|0|\n328|GY|GUY|EN|0|\n332|HT|HTI|FR,HT|0|\n334|HM|HMD|EN|1|\n336|VA|VAT|IT|0|\n340|HN|HND|ES|0|\n344|HK|HKG|EN,ZH|1|\n348|HU|HUN|HU,EN|0|\n352|IS|ISL|IS,EN|0|\n356|IN|IND|EN,HI|0|\n360|ID|IDN|ID|0|\n364|IR|IRN|FA|0|\n368|IQ|IRQ|AR,KU|0|\n372|IE|IRL|EN,GA|0|\n376|IL|ISR|AR,HE|0|\n380|IT|ITA|IT|0|\n384|CI|CIV|FR|0|\n388|JM|JAM|EN|1|\n392|JP|JPN|JA,EN|0|\n398|KZ|KAZ|KK,RU|0|\n400|JO|JOR|AR|0|\n404|KE|KEN|EN,SW|0|\n408|KP|PRK|KO|1|\n410|KR|KOR|KO|0|\n414|KW|KWT|AR|0|\n417|KG|KGZ|UZ,KY,RU|0|\n418|LA|LAO|LO|0|\n422|LB|LBN|AR,EN,FR|1|\n426|LS|LSO|EN|0|\n428|LV|LVA|LV|0|\n430|LR|LBR|EN|0|\n434|LY|LBY|AR|0|\n438|LI|LIE|DE|0|\n440|LT|LTU|LT|0|\n442|LU|LUX|LB,FR,DE|0|\n446|MO|MAC|PT,ZH|1|\n450|MG|MDG|MG,FR,EN|0|\n454|MW|MWI|EN|1|\n458|MY|MYS|MS|0|\n462|MV|MDV|DV|0|\n466|ML|MLI|FR|1|\n470|MT|MLT|MT,EN|0|\n474|MQ|MTQ|FR|0|\n478|MR|MRT|AR|1|\n480|MU|MUS|EN,FR|0|\n484|MX|MEX|ES|0|\n492|MC|MCO|FR|0|\n496|MN|MNG|MN|0|\n498|MD|MDA|MO|0|\n499|ME|MNE|SR|0|\n500|MS|MSR|EN|1|\n504|MA|MAR|AR|0|\n508|MZ|MOZ|PT|0|\n512|OM|OMN|AR|0|\n516|NA|NAM|AF,EN|1|\n520|NR|NRU|NA|1|\n524|NP|NPL|NE|0|\n528|NL|NLD|NL|0|\n530|AN|ANT|NL|1|\n531|CW|CUW|EN,NL|1|\n533|AW|ABW|EN|1|\n534|SX|SXM|EN,NL|1|\n535|BQ|BES|NL|1|\n540|NC|NCL|FR|0|\n548|VU|VUT|FR,EN|0|\n554|NZ|NZL|EN|0|\n558|NI|NIC|ES|0|\n562|NE|NER|FR|0|\n566|NG|NGA|EN|0|\n570|NU|NIU|EN|1|\n574|NF|NFK|EN|1|\n578|NO|NOR|NO,NB,NN|0|\n580|MP|MNP|EN|1|\n581|UM|UMI|EN|1|\n583|FM|FSM|EN|0|\n584|MH|MHL|EN|1|\n585|PW|PLW|EN|0|\n586|PK|PAK|EN|0|\n591|PA|PAN|ES|1|\n598|PG|PNG|EN|0|\n600|PY|PRY|ES,GN|0|\n604|PE|PER|ES,QU,AY|0|\n608|PH|PHL|EN|0|\n612|PN|PCN|EN|0|\n616|PL|POL|PL|0|\n620|PT|PRT|PT|0|\n624|GW|GNB|PT|0|\n626|TL|TLS|PT,ID|1|\n630|PR|PRI|ES,EN|0|\n634|QA|QAT|AR|1|\n638|RE|REU|FR|0|\n642|RO|ROU|RO|0|\n643|RU|RUS|RU|0|\n646|RW|RWA|FR,EN,RW|1|\n654|SH|SHN|EN|0|\n659|KN|KNA|EN|1|\n660|AI|AIA|EN|1|\n662|LC|LCA|EN|1|\n663|MF|MAF|FR|0|\n666|PM|SPM|FR|0|\n670|VC|VCT|EN|1|\n674|SM|SMR|IT|0|\n678|ST|STP|PT|1|\n682|SA|SAU|AR|0|\n686|SN|SEN|FR|0|\n688|RS|SRB|SR|0|\n690|SC|SYC|FR,EN|1|\n694|SL|SLE|EN|1|\n702|SG|SGP|EN,MS,TA|0|\n703|SK|SVK|SK|0|\n704|VN|VNM|VI|0|\n705|SI|SVN|SL|0|\n706|SO|SOM|SO,EN|0|\n710|ZA|ZAF|AF,EN|0|\n716|ZW|ZWE|EN|1|\n724|ES|ESP|ES|0|\n728|SS|SSD|EN|0|\n729|SD|SDN|AR,EN|0|\n732|EH|ESH|AR,ES,FR|0|\n740|SR|SUR|EN,NL|1|\n744|SJ|SJM|NO|0|\n748|SZ|SWZ|EN,SS|0|\n752|SE|SWE|SV|0|\n756|CH|CHE|DE,FR,IT|0|\n760|SY|SYR|AR|0|\n762|TJ|TJK|TG|0|\n764|TH|THA|TH|0|\n768|TG|TGO|FR|1|\n772|TK|TKL|EN|0|\n776|TO|TON|EN,TO|1|\n780|TT|TTO|EN|0|\n784|AE|ARE|AR|1|\n788|TN|TUN|AR|0|\n792|TR|TUR|TR|0|\n795|TM|TKM|TK|0|\n796|TC|TCA|EN|0|\n798|TV|TUV|EN|1|\n800|UG|UGA|EN,SW|1|\n804|UA|UKR|UK,RU|0|\n807|MK|MKD|MK|0|\n818|EG|EGY|AR|0|\n826|GB|GBR|EN|0|\n831|GG|GGY|EN|0|\n832|JE|JEY|EN|0|\n833|IM|IMN|EN|0|\n834|TZ|TZA|EN,SW|1|\n840|US|USA|EN|0|\n850|VI|VIR|EN|0|\n854|BF|BFA|FR|0|\n858|UY|URY|ES|0|\n860|UZ|UZB|UZ|0|\n862|VE|VEN|ES|0|\n876|WF|WLF|FR|0|\n882|WS|WSM|SM|1|\n887|YE|YEM|AR|0|\n894|ZM|ZMB|EN|0|\n991|IC|ISC|ES|0|\n999|ZZ|ZZZ|EN|0|\n"
  },
  {
    "path": "pyshipping/carriers/dpd/georoutetables/LOCATION.DE",
    "content": "#Filename: LOCATION.DE\n#Version: 20140505\n#Expiration: 20140831\n#Hash: 6eedc807e450c4e8a76ee039ff22226d38b95f30\n#Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt\n#Fields: AreaName|CityName|ISO-Alpha2CountryCode|PostCode|\n#Key: AreaName|CityName|\n|Dublin|IE|1|\n|Irland ohne Dublin|IE|2|\n"
  },
  {
    "path": "pyshipping/carriers/dpd/georoutetables/LOCATION.EN",
    "content": "#Filename: LOCATION.EN\n#Version: 20140505\n#Expiration: 20140831\n#Hash: 6f7eff135d73b9bf60bc6d9c983313d8811fbcb2\n#Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt\n#Fields: AreaName|CityName|ISO-Alpha2CountryCode|PostCode|\n#Key: AreaName|CityName|\n|Dublin|IE|1|\n|Ireland excluding Dublin|IE|2|\n"
  },
  {
    "path": "pyshipping/carriers/dpd/georoutetables/LOCATION.FR",
    "content": "#Filename: LOCATION.FR\n#Version: 20140505\n#Expiration: 20140831\n#Hash: 71ba3db7983f8bcfa2a1b1cd327572ffcafaa331\n#Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt\n#Fields: AreaName|CityName|ISO-Alpha2CountryCode|PostCode|\n#Key: AreaName|CityName|\n|Dublin|IE|1|\n|Reste de l'Irlande (sauf Dublin)|IE|2|\n"
  },
  {
    "path": "pyshipping/carriers/dpd/georoutetables/SERVICE",
    "content": "#Filename: SERVICE\n#Version: 20140505\n#Expiration: 20140831\n#Hash: c8aa7333314e4057f7cf43d42ec66e9f89588345\n#Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt\n#Fields: ServiceCode|ServiceText|ServiceMark|ServiceElements|\n#Key: ServiceCode|\n101|D||001|\n102|D-HAZ||001,150|\n103|D-6||001,130|\n104|D-6-HAZ||001,130,150|\n105|D-EXW||001,110|\n106|D-EXW-HAZ||001,110,150|\n107|D-6-EXW||001,110,130|\n108|D-6-EXW-HAZ||001,110,130,150|\n109|D-COD||001,100|\n110|D-COD-HAZ||001,100,150|\n111|D-6-COD||001,100,130|\n112|D-6-COD-HAZ||001,100,130,150|\n113|D-SWAP||001,120|\n114|D-SWAP-HAZ||001,120,150|\n115|D-6-SWAP||001,120,130|\n116|D-6-SWAP-HAZ||001,120,130,150|\n117|D||001,170|\n118|D-BACK||001,121|\n119|D-BACK-HAZ||001,121,150|\n120|D+||001,180|\n121|D-HAZ+||001,150,180|\n122|D-6+||001,130,180|\n123|D-6-HAZ+||001,130,150,180|\n124|D-EXW+||001,110,180|\n125|D-EXW-HAZ+||001,110,150,180|\n126|D-6-EXW+||001,110,130,180|\n127|D-6-EXW-HAZ+||001,110,130,150,180|\n128|D-COD+||001,100,180|\n129|D-COD-HAZ+||001,100,150,180|\n130|D-6-COD+||001,100,130,180|\n131|D-6-COD-HAZ+||001,100,130,150,180|\n132|D-SWAP+||001,120,180|\n133|D-SWAP-HAZ+||001,120,150,180|\n134|D-6-SWAP+||001,120,130,180|\n135|D-6-SWAP-HAZ+||001,120,130,150,180|\n136|D|X|002|\n137|D-6|X|002,130|\n138|D-EXW|X|002,110|\n139|D-6-EXW|X|002,110,130|\n140|D-COD|X|002,100|\n141|D-6-COD|X|002,100,130|\n142|D-SWAP|X|002,120|\n143|D-6-SWAP|X|002,120,130|\n144|D|X|002,170|\n145|D-BACK|X|002,121|\n146|D+|X|002,180|\n147|D-6+|X|002,130,180|\n148|D-EXW+|X|002,110,180|\n149|D-6-EXW+|X|002,110,130,180|\n150|D-COD+|X|002,100,180|\n151|D-6-COD+|X|002,100,130,180|\n152|D-SWAP+|X|002,120,180|\n153|D-6-SWAP+|X|002,120,130,180|\n154|PARCELLetter|X|005|\n155|PM2||010|\n156|PM2-NO||010,160|\n157|PM2-HAZ||010,150|\n158|PM2-EXW||010,110|\n159|PM2-EXW-NO||010,110,160|\n160|PM2-EXW-HAZ||010,110,150|\n161|PM2-COD||010,100|\n162|PM2-COD-NO||010,100,160|\n163|PM2-COD-HAZ||010,100,150|\n164|PM2-SWAP||010,120|\n165|PM2-SWAP-HAZ||010,120,150|\n166|PM2-BACK||010,121|\n167|PM2-BACK-HAZ||010,121,150|\n168|PM2+||010,180|\n169|PM2-NO+||010,160,180|\n170|PM2-HAZ+||010,150,180|\n171|PM2-EXW+||010,110,180|\n172|PM2-EXW-NO+||010,110,160,180|\n173|PM2-EXW-HAZ+||010,110,150,180|\n174|PM2-COD+||010,100,180|\n175|PM2-COD-NO+||010,100,160,180|\n176|PM2-COD-HAZ+||010,100,150,180|\n177|PM2-SWAP+||010,120,180|\n178|PM2-SWAP-HAZ+||010,120,150,180|\n179|AM1||022|\n180|AM1-NO||022,160|\n181|AM1-HAZ||022,150|\n182|AM1-6||022,130|\n183|AM1-6-NO||022,130,160|\n184|AM1-6-HAZ||022,130,150|\n185|AM1-EXW||022,110|\n186|AM1-EXW-NO||022,110,160|\n187|AM1-EXW-HAZ||022,110,150|\n188|AM1-6-EXW||022,110,130|\n189|AM1-6-EXW-NO||022,110,130,160|\n190|AM1-6-EXW-HAZ||022,110,130,150|\n191|AM1-COD||022,100|\n192|AM1-COD-NO||022,100,160|\n193|AM1-COD-HAZ||022,100,150|\n194|AM1-6-COD||022,100,130|\n195|AM1-6-COD-NO||022,100,130,160|\n196|AM1-6-COD-HAZ||022,100,130,150|\n197|AM1-SWAP||022,120|\n198|AM1-SWAP-HAZ||022,120,150|\n199|AM1-6-SWAP||022,120,130|\n200|AM1-6-SWAP-HAZ||022,120,130,150|\n201|AM1-BACK||022,121|\n202|AM1-BACK-HAZ||022,121,150|\n203|AM1+||022,180|\n204|AM1-NO+||022,160,180|\n205|AM1-HAZ+||022,150,180|\n206|AM1-6+||022,130,180|\n207|AM1-6-NO+||022,130,160,180|\n208|AM1-6-HAZ+||022,130,150,180|\n209|AM1-EXW+||022,110,180|\n210|AM1-EXW-NO+||022,110,160,180|\n211|AM1-EXW-HAZ+||022,110,150,180|\n212|AM1-6-EXW+||022,110,130,180|\n213|AM1-6-EXW-NO+||022,110,130,160,180|\n214|AM1-6-EXW-HAZ+||022,110,130,150,180|\n215|AM1-COD+||022,100,180|\n216|AM1-COD-NO+||022,100,160,180|\n217|AM1-COD-HAZ+||022,100,150,180|\n218|AM1-6-COD+||022,100,130,180|\n219|AM1-6-COD-NO+||022,100,130,160,180|\n220|AM1-6-COD-HAZ+||022,100,130,150,180|\n221|AM1-SWAP+||022,120,180|\n222|AM1-SWAP-HAZ+||022,120,150,180|\n223|AM1-6-SWAP+||022,120,130,180|\n224|AM1-6-SWAP-HAZ+||022,120,130,150,180|\n225|AM2||023|\n226|AM2-NO||023,160|\n227|AM2-HAZ||023,150|\n228|AM2-6||023,130|\n229|AM2-6-NO||023,130,160|\n230|AM2-6-HAZ||023,130,150|\n231|AM2-EXW||023,110|\n232|AM2-EXW-NO||023,110,160|\n233|AM2-EXW-HAZ||023,110,150|\n234|AM2-6-EXW||023,110,130|\n235|AM2-6-EXW-NO||023,110,130,160|\n236|AM2-6-EXW-HAZ||023,110,130,150|\n237|AM2-COD||023,100|\n238|AM2-COD-NO||023,100,160|\n239|AM2-COD-HAZ||023,100,150|\n240|AM2-6-COD||023,100,130|\n241|AM2-6-COD-NO||023,100,130,160|\n242|AM2-6-COD-HAZ||023,100,130,150|\n243|AM2-SWAP||023,120|\n244|AM2-SWAP-HAZ||023,120,150|\n245|AM2-6-SWAP||023,120,130|\n246|AM2-6-SWAP-HAZ||023,120,130,150|\n247|AM2-BACK||023,121|\n248|AM2-BACK-HAZ||023,121,150|\n249|AM2+||023,180|\n250|AM2-NO+||023,160,180|\n251|AM2-HAZ+||023,150,180|\n252|AM2-6+||023,130,180|\n253|AM2-6-NO+||023,130,160,180|\n254|AM2-6-HAZ+||023,130,150,180|\n255|AM2-EXW+||023,110,180|\n256|AM2-EXW-NO+||023,110,160,180|\n257|AM2-EXW-HAZ+||023,110,150,180|\n258|AM2-6-EXW+||023,110,130,180|\n259|AM2-6-EXW-NO+||023,110,130,160,180|\n260|AM2-6-EXW-6-HAZ+||023,110,130,150,180|\n261|AM2-COD+||023,100,180|\n262|AM2-COD-NO+||023,100,160,180|\n263|AM2-COD-HAZ+||023,100,150,180|\n264|AM2-6-COD+||023,100,130,180|\n265|AM2-6-COD-NO+||023,100,130,160,180|\n266|AM2-6-COD-HAZ+||023,100,130,150,180|\n267|AM2-SWAP+||023,120,180|\n268|AM2-SWAP-HAZ+||023,120,150,180|\n269|AM2-6-SWAP+||023,120,130,180|\n270|AM2-6-SWAP-HAZ+||023,120,130,150,180|\n271|SD||040|\n272|SD-HAZ||040,150|\n273|SD-EXW||040,110|\n274|SD-EXW-HAZ||040,110,150|\n275|SD-COD||040,100|\n276|SD-COD-HAZ||040,100,150|\n277|SD-SWAP||040,120|\n278|SD-SWAP-HAZ||040,120,150|\n279|SD-BACK||040,121|\n280|SD-BACK-HAZ||040,121,150|\n281|SD+||040,180|\n282|SD-HAZ+||040,150,180|\n283|SD-EXW+||040,110,180|\n284|SD-EXW-HAZ+||040,110,150,180|\n285|SD-COD+||040,100,180|\n286|SD-COD-HAZ+||040,100,150,180|\n287|SD-SWAP+||040,120,180|\n288|SD-SWAP-HAZ+||040,120,150,180|\n289|STANDARD BAG||060|\n290|MAIL BAG||005,060|\n291|IP||080|\n292|POD BOX||081|\n293|EXPRESS BAG||020,060|\n294|MAIL||006,060|\n298|RETURN SENDER||070,071|\n299|RETURN|E|020,070|\n300|RETURN||070|\n301|RETURN-HAZ||070,150|\n302|IE2|E|030|\n303|IE2-MPS|E|030,140|\n304|IE2-6|E|030,130|\n305|IE2-6-MPS|E|030,130,140|\n306|IE2-SWAP|E|030,120|\n307|IE2-SWAP-MPS|E|030,120,140|\n308|IE2-6-SWAP|E|030,120,130|\n309|IE2-6-SWAP-MPS|E|030,120,130,140|\n310|IE2-COD|E|030,100|\n311|IE2-COD-MPS|E|030,100,140|\n312|IE2-6-COD|E|030,100,130|\n313|IE2-6-COD-MPS|E|030,100,130,140|\n314|IE1|E|031|\n325|D||001,012|\n326|D|X|002,012|\n327|D-B2C||001,013|\n328|D-B2C|X|002,013|\n329|D-COD-B2C||001,013,100|\n330|D-COD-B2C|X|002,013,100|\n331|D-TYRE UNP||001,014|\n332|RETURN||072|\n333|D-B2C+||001,013,180|\n334|D-B2C+|X|002,013,180|\n335|D-COD-B2C+||001,013,100,180|\n336|D-COD-B2C+|X|002,013,100,180|\n337|D-B2C-PSD||001,013,200|\n338|D-B2C-PSD|X|002,013,200|\n340|DPD MAX||090|\n341|D-B2C-COD-PSD||001,013,100,200|\n342|D-B2C-COD-PSD|X|002,013,100,200|\n350|AM0||021|\n351|AM0-EXW||021,110|\n352|AM0-COD||021,100|\n353|AM0-SWAP||021,120|\n354|AM0-BACK||021,121|\n355|AM0+||021,180|\n356|AM0-EXW+||021,110,180|\n357|AM0-COD+||021,100,180|\n358|AM0-SWAP+||021,120,180|\n359|D||001,190|\n360|D|X|002,190|\n361|D||001,110,190|\n362|D|X|002,110,190|\n363|D||001,210|\n364|D|X|002,210|\n365|D-TYRE||001,240|\n366|D-TYRE-B2C||001,240,013|\n367|D-TYRE-COD||001,240,100|\n800|EXP||020|\n801|EXP-COD||020,100|\n802|EXP-EXW||020,110|\n803|PS||610|\n804|PS-COD||610,100|\n805|PS-EXP||610,020|\n806|D-SWAP-B2C+||001,013,120,180|\n807|D-6-SWAP-B2C+||001,013,120,130,180|\n808|D-SWAP-COD-B2C+||001,013,100,120,180|\n809|D6-SWAP-COD-B2C+||001,013,100,120,130,180|\n810|AM1||022|\n811|AM2||023|\n812|PM2||010|\n813|TFR||600|\n814|AM1-6||022,130|\n815|AM2-6||023,130|\n816|FIX||601|\n817|PRIVAT||602|\n818|D-SWAP-COD+||001,100,120,180|\n819|D-6-SWAP-COD+||001,100,120,130,180|\n820|D-6-B2C+||001,013,130,180|\n821|D-6-B2C+|X|002,013,130,180|\n822|D-6-COD-B2C+||001,013,100,130,180|\n823|D-6-COD-B2C+|X|002,013,100,130,180|\n824|D-ECO||001,250|\n825|DPD MAX||090|\n826|DPD MAX - COD||090,100|\n827|D-SWAP-B2C||001,013,120|\n828|D-SWAP-B2C|X|002,013,120|\n829|D-6-B2C||001,013,130|\n830|D-6-B2C|X|002,013,130|\n831|D-6-COD-B2C||001,013,100,130|\n832|D-6-COD-B2C|X|002,013,100,130|\n833|D-6-SWAP-B2C||001,013,120,130|\n834|D-6-SWAP-B2C|X|002,013,120,130|\n835|D-SWAP-COD-B2C||001,013,100,120|\n836|D-SWAP-COD-B2C|X|002,013,100,120|\n837|D-6-SWAP-COD-B2C||001,013,100,120,130|\n838|D-6-SWAP-COD-B2C|X|002,013,100,120,130|\n839|D-EVE||001,220|\n840|D-EVE|X|002,220|\n841|D-COD-EVE||001,100,220|\n842|D-COD-EVE|X|002,100,220|\n843|SPAL||230|\n844|D-B2C-PRIV||001,013,620|\n845|D-B2C-PRIV|X|002,013,620|\n846|D-B2C-HOME||001,013,621|\n847|D-B2C-HOME|X|002,013,621|\n848|D-SWAP-COD||001,100,120|\n849|D-ECO-COD||001,250,100|\n850|AM2-ECH-SHOP-NO||023,529,511,160|\n"
  },
  {
    "path": "pyshipping/carriers/dpd/georoutetables/SERVICEINFO.CS",
    "content": "#Filename: SERVICEINFO.CS\n#Version: 20140505\n#Expiration: 20140831\n#Hash: 755fd81761d0474d68f00ff70ce4516fa8fef173\n#Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt\n#Fields: ServiceCode|ServiceFieldInfo|\n#Key: ServiceCode|\n102|nebezpecne zbozi / hazardous goods|\n103|sobota /saturday|\n106|ex works nebezpecne zbozi / ex works hazardous goods|\n109|dobirka / C.O.D.|\n110|dobirka nebezpecne zbozi / C.O.D. hazardous goods|\n113|vymena / exchange|\n117|Collection-upon-Delivery|\n121|nebezpecne zbozi / hazardous goods|\n124|ex works|\n125|ex works nebezpecne zbozi / ex works hazardous goods|\n128|dobirka / C.O.D.|\n129|dobirka  nebezpecne zbozi / C.O.D. hazardous goods|\n132|vymena / exchange|\n137|sobota / saturday|\n138|ex works|\n140|dobirka / C.O.D.|\n142|vymena / exchange|\n144|Collection-upon-Delivery|\n148|ex works|\n150|dobirka / C.O.D.|\n152|vymena / exchange|\n154|DPD PARCELLetter|\n155|DPD 18:00 / DPD GUARANTEE|\n158|DPD 18:00 ex works / DPD GUARANTEE ex works|\n161|DPD 18:00 dobirka / DPD GUARANTEE C.O.D.|\n164|DPD 18:00 vymena / DPD GUARANTEE exchange|\n168|DPD 18:00 / DPD GUARANTEE|\n171|DPD 18:00 ex works / DPD GUARANTEE ex works|\n174|DPD 18:00 dobirka / DPD GUARANTEE C.O.D.|\n179|DPD 10:00|\n185|DPD 10:00 ex works|\n191|DPD 10:00 dobirka / C.O.D.|\n197|DPD 10:00 vymena / exchange|\n203|DPD 10:00|\n209|DPD 10:00 ex works|\n215|DPD 10:00 dobirka / C.O.D.|\n225|DPD 12:00|\n228|DPD 12:00 sobota / Saturday|\n231|DPD 12:00 ex works|\n234|DPD 12:00 sobota ex works / Saturday ex works|\n237|DPD 12:00 dobirka / C.O.D.|\n240|DPD 12:00 sobota dobirka / Saturday C.O.D.|\n243|DPD 12:00 vymena  / exchange|\n249|DPD 12:00|\n252|DPD 12:00 sobota / Saturday|\n255|DPD 12:00 ex works|\n258|DPD 12:00 sobota ex works / Saturday ex works|\n261|DPD 12:00 dobirka / C.O.D.|\n271|stejny den / same day|\n327|PRIVATE ADDRESS / B2C|\n328|PRIVATE ADDRESS / B2C|\n329|PRIVATE ADDRESS / B2C - C.O.D.|\n330|PRIVATE ADDRESS / B2C - C.O.D.|\n333|SOUKROMA ADRESA / KONTAKTOVAT PRIJEMCE|\n334|SOUKROMA ADRESA / KONTAKTOVAT PRIJEMCE|\n335|SOUKROMA ADRESA DOBIRKA / KONTAKTOVAT PRIJEMCE|\n336|SOUKROMA ADRESA DOBIRKA / KONTAKTOVAT PRIJEMCE|\n352|DPD 8:30 dobirka / C.O.D.|\n353|DPD 8:30 vymena / exchange|\n357|DPD 8:30 dobirka / C.O.D.|\n358|DPD 8:30 vymena / exchange|\n839|VECERNI DORUCENI 17:00-20:00|\n840|VECERNI DORUCENI 17:00-20:00|\n841|VECERNI DORUCENI 17:00-20:00 DOBIRKA|\n842|VECERNI DORUCENI 17:00-20:00 DOBIRKA|\n"
  },
  {
    "path": "pyshipping/carriers/dpd/georoutetables/SERVICEINFO.CZ",
    "content": "#Filename: SERVICEINFO.CZ\n#Version: 20110905\n#Expiration: 20120101\n#Hash: de42bef2636b38cd7f371000f71df64f66cc0eda\n#Reference: http://extranet.dpd.de/georoute/references_dpd_20110905.txt\n#Fields: ServiceCode|ServiceFieldInfo|\n#Key: ServiceCode|\n102|nebezpecne zbozi / hazardous goods|\n103|sobota /saturday|\n106|ex works nebezpecne zbozi / ex works hazardous goods|\n109|dobirka / C.O.D.|\n110|dobirka nebezpecne zbozi / C.O.D. hazardous goods|\n113|vymena / exchange|\n117|Collection-upon-Delivery|\n121|nebezpecne zbozi / hazardous goods|\n124|ex works|\n125|ex works nebezpecne zbozi / ex works hazardous goods|\n128|dobirka / C.O.D.|\n129|dobirka  nebezpecne zbozi / C.O.D. hazardous goods|\n132|vymena / exchange|\n137|sobota / saturday|\n138|ex works|\n140|dobirka / C.O.D.|\n142|vymena / exchange|\n144|Collection-upon-Delivery|\n148|ex works|\n150|dobirka / C.O.D.|\n152|vymena / exchange|\n154|DPD PARCELLetter|\n155|DPD 18:00 / DPD GUARANTEE|\n158|DPD 18:00 ex works / DPD GUARANTEE ex works|\n161|DPD 18:00 dobirka / DPD GUARANTEE C.O.D.|\n164|DPD 18:00 vymena / DPD GUARANTEE exchange|\n168|DPD 18:00 / DPD GUARANTEE|\n171|DPD 18:00 ex works / DPD GUARANTEE ex works|\n174|DPD 18:00 dobirka / DPD GUARANTEE C.O.D.|\n179|DPD 10:00|\n185|DPD 10:00 ex works|\n191|DPD 10:00 dobirka / C.O.D.|\n197|DPD 10:00 vymena / exchange|\n203|DPD 10:00|\n209|DPD 10:00 ex works|\n215|DPD 10:00 dobirka / C.O.D.|\n225|DPD 12:00|\n228|DPD 12:00 sobota / Saturday|\n231|DPD 12:00 ex works|\n234|DPD 12:00 sobota ex works / Saturday ex works|\n237|DPD 12:00 dobirka / C.O.D.|\n240|DPD 12:00 sobota dobirka / Saturday C.O.D.|\n243|DPD 12:00 vymena  / exchange|\n249|DPD 12:00|\n252|DPD 12:00 sobota / Saturday|\n255|DPD 12:00 ex works|\n258|DPD 12:00 sobota ex works / Saturday ex works|\n261|DPD 12:00 dobirka / C.O.D.|\n271|stejny den / same day|\n327|SOUKROMA ADRESA / KONTAKTOVAT PRIJEMCE|\n328|SOUKROMA ADRESA / KONTAKTOVAT PRIJEMCE|\n329|SOUKROMA ADRESA DOBIRKA / KONTAKTOVAT PRIJEMCE|\n330|SOUKROMA ADRESA DOBIRKA / KONTAKTOVAT PRIJEMCE|\n333|SOUKROMA ADRESA / KONTAKTOVAT PRIJEMCE|\n334|SOUKROMA ADRESA / KONTAKTOVAT PRIJEMCE|\n335|SOUKROMA ADRESA DOBIRKA / KONTAKTOVAT PRIJEMCE|\n336|SOUKROMA ADRESA DOBIRKA / KONTAKTOVAT PRIJEMCE|\n352|DPD 8:30 dobirka / C.O.D.|\n353|DPD 8:30 vymena / exchange|\n357|DPD 8:30 dobirka / C.O.D.|\n358|DPD 8:30 vymena / exchange|\n"
  },
  {
    "path": "pyshipping/carriers/dpd/georoutetables/SERVICEINFO.DE",
    "content": "#Filename: SERVICEINFO.DE\n#Version: 20140505\n#Expiration: 20140831\n#Hash: 8124d0f60be0fe5c28a7f0af019c975fc2bd3542\n#Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt\n#Fields: ServiceCode|ServiceFieldInfo|\n#Key: ServiceCode|\n102|Gefahrgut / hazardous goods|\n103|Samstag / saturday|\n105|Unfrei / ex works|\n106|Unfrei / ex works Gefahrgut / hazardous goods|\n109|Nachnahme / C.O.D.|\n110|Nachnahme / C.O.D. Gefahrgut / hazardous goods|\n113|Austausch / exchange|\n117|Mitnahme/Collection-upon-Delivery|\n121|Gefahrgut / hazardous goods|\n124|Unfrei / ex works|\n125|Unfrei / ex works Gefahrgut / hazardous goods|\n128|Nachnahme / C.O.D.|\n129|Nachnahme / C.O.D. Gefahrgut / hazardous goods|\n132|Austausch / exchange|\n137|Samstag / saturday|\n138|Unfrei / ex works|\n140|Nachnahme / C.O.D.|\n142|Austausch / exchange|\n144|Mitnahme/Collection-upon-Delivery|\n148|Unfrei / ex works|\n150|Nachnahme / C.O.D.|\n152|Austausch / exchange|\n154|DPD PARCELLetter|\n155|DPD 18:00 / DPD GUARANTEE|\n158|DPD 18:00 / DPD GUARANTEE Unfrei / ex works|\n161|DPD 18:00 / DPD GUARANTEE Nachnahme / C.O.D.|\n164|DPD 18:00 / DPD GUARANTEE Austausch / exchange|\n168|DPD 18:00 / DPD GUARANTEE|\n171|DPD 18:00 / DPD GUARANTEE Unfrei / ex works|\n174|DPD 18:00 / DPD GUARANTEE Nachnahme / C.O.D.|\n179|DPD 10:00|\n185|DPD 10:00 Unfrei / ex works|\n191|DPD 10:00 Nachnahme / C.O.D.|\n197|DPD 10:00 Austausch / exchange|\n203|DPD 10:00|\n209|DPD 10:00 Unfrei / ex works|\n215|DPD 10:00 Nachnahme / C.O.D.|\n225|DPD 12:00|\n228|DPD 12:00 Samstag / saturday|\n231|DPD 12:00 Unfrei / ex works|\n234|DPD 12:00 Samstag / saturday Unfrei / ex works|\n237|DPD 12:00 Nachnahme / C.O.D.|\n240|DPD 12:00 Samstag / saturday Nachnahme / C.O.D.|\n243|DPD 12:00 Austausch / exchange|\n249|DPD 12:00|\n252|DPD 12:00 Samstag / saturday|\n255|DPD 12:00 Unfrei / ex works|\n258|DPD 12:00 Samstag / saturday Unfrei / ex works|\n261|DPD 12:00 Nachnahme / C.O.D.|\n264|DPD 12:00 Samstag / saturday Nachnahme / C.O.D.|\n270|DPD 12:00 Samstag Austausch Gefahrgut / Saturday Swap Hazardous Goods|\n271|same day|\n294|DPD Mail|\n302|DPD EXPRESS|\n325|EXPRESS ECONOMY|\n326|EXPRESS ECONOMY|\n329|Nachnahme / C.O.D.|\n330|Nachnahme / C.O.D.|\n335|Nachnahme / C.O.D.|\n336|Nachnahme / C.O.D.|\n337|DPD PaketShop Zustellung / Parcel Shop Delivery|\n338|DPD PaketShop Zustellung / Parcel Shop Delivery|\n341|DPD PaketShop Zustellung Nachnahme / Parcel Shop Delivery C.O.D.|\n342|DPD PaketShop Zustellung Nachnahme / Parcel Shop Delivery C.O.D.|\n350|DPD 8:30|\n351|DPD 8:30 Unfrei / ex works|\n352|DPD 8:30 Nachnahme / C.O.D.|\n353|DPD 8:30 Austausch / exchange|\n354|DPD 8:30|\n355|DPD 8:30|\n356|DPD 8:30 Unfrei / ex works|\n357|DPD 8:30 Nachnahme / C.O.D.|\n358|DPD 8:30 Austausch / exchange|\n361|Unfrei / ex works|\n362|Unfrei / ex works|\n365|Reifen / Tyre|\n366|Reifen / Tyre B2C|\n367|Reifen / Tyre Nachnahme / C.O.D.|\n810|Service Werktag 09:00 Uhr|\n811|Service Werktag 12:00 Uhr|\n812|Service Werktag 17:00 Uhr|\n813|Service Werktag Zeitfenster|\n814|Service Samstag 09:00 Uhr|\n815|Service Samstag 12:00 Uhr|\n816|Service Fixtermin|\n817|Privatpaket|\n820|Samstag / saturday|\n821|Samstag / saturday|\n822|Samstag / saturday Nachnahme / C.O.D.|\n823|Samstag / saturday Nachnahme / C.O.D.|\n827|Austausch / exchange|\n828|Austausch / exchange|\n829|Samstag / saturday|\n830|Samstag / saturday|\n831|Samstag / saturday Nachnahme / C.O.D.|\n832|Samstag / saturday Nachnahme / C.O.D.|\n833|Samstag / saturday Austausch / exchange|\n834|Samstag / saturday Austausch / exchange|\n835|Austausch / exchange Nachnahme / C.O.D.|\n836|Austausch / exchange Nachnahme / C.O.D.|\n837|Samstag / saturday Austausch / exchange Nachnahme / C.O.D.|\n838|Samstag / saturday Austausch / exchange Nachnahme / C.O.D.|\n839|Abendzustellung / Evening Delivery|\n840|Abendzustellung / Evening Delivery|\n841|Abendzustellung Nachnahme / Evening Delivery C.O.D.|\n842|Abendzustellung Nachnahme / Evening Delivery C.O.D.|\n844|Consumer Home|\n845|Consumer Home|\n846|Consumer Home & Shop|\n847|Consumer Home & Shop|\n"
  },
  {
    "path": "pyshipping/carriers/dpd/georoutetables/SERVICEINFO.EE",
    "content": "#Filename: SERVICEINFO.EE\n#Version: 20110905\n#Expiration: 20120101\n#Hash: d8d55ca2e78b6aad2ecb2a4169dd58b108007c6e\n#Reference: http://extranet.dpd.de/georoute/references_dpd_20110905.txt\n#Fields: ServiceCode|ServiceFieldInfo|\n#Key: ServiceCode|\n102|hazardous goods|\n103|saturday|\n105|ex works|\n106|ex works / hazardous goods|\n109|Lunapakk (COD)|\n110|Lunapakk (COD) / hazardous goods|\n113|exchange|\n117|Collection-upon-Delivery|\n121|hazardous goods|\n124|ex works|\n125|ex works / hazardous goods|\n128|Lunapakk (COD)|\n129|Lunapakk (COD) / hazardous goods|\n132|exchange|\n137|saturday|\n138|ex works|\n140|Lunapakk (COD)|\n142|exchange|\n144|Collection-upon-Delivery|\n148|ex works|\n150|Lunapakk (COD)|\n152|exchange|\n154|DPD PARCELLetter|\n155|DPD GUARANTEE|\n158|DPD GUARANTEE ex works|\n161|DPD GUARANTEE Lunapakk (COD)|\n164|DPD GUARANTEE exchange|\n168|DPD GUARANTEE|\n171|DPD GUARANTEE ex works|\n174|DPD GUARANTEE Lunapakk (COD)|\n179|DPD 10:00|\n185|DPD 10:00 ex works|\n191|DPD 10:00 Lunapakk (COD)|\n197|DPD 10:00 exchange|\n203|DPD 10:00|\n209|DPD 10:00 ex works|\n215|DPD 10:00 Lunapakk (COD)|\n225|DPD 12:00|\n228|DPD 12:00 saturday|\n231|DPD 12:00 ex works|\n234|DPD 12:00 saturday / ex works|\n237|DPD 12:00 Lunapakk (COD)|\n240|DPD 12:00 saturday / Lunapakk (COD)|\n243|DPD 12:00 exchange|\n249|DPD 12:00|\n252|DPD 12:00 saturday|\n255|DPD 12:00 ex works|\n258|DPD 12:00 saturday / ex works|\n261|DPD 12:00 Lunapakk (COD)|\n264|DPD 12:00 saturday / Lunapakk (COD)|\n271|same day|\n294|DPD Mail|\n302|DPD EXPRESS|\n325|EXPRESS ECONOMY|\n326|EXPRESS ECONOMY|\n329|Lunapakk (COD)|\n330|Lunapakk (COD)|\n335|Lunapakk (COD)|\n336|Lunapakk (COD)|\n350|DPD 8:30|\n351|DPD 8:30 ex works|\n352|DPD 8:30 Lunapakk (COD)|\n353|DPD 8:30 exchange|\n354|DPD 8:30|\n355|DPD 8:30|\n356|DPD 8:30 ex works|\n357|DPD 8:30 Lunapakk (COD)|\n358|DPD 8:30 exchange|\n820|saturday|\n821|saturday|\n822|saturday / Lunapakk (COD)|\n823|saturday / Lunapakk (COD)|\n827|exchange|\n828|exchange|\n829|saturday|\n830|saturday|\n831|saturday / Lunapakk (COD)|\n832|saturday / Lunapakk (COD)|\n833|saturday / exchange|\n834|saturday / exchange|\n835|exchange / Lunapakk (COD)|\n836|exchange / Lunapakk (COD)|\n837|saturday / exchange / Lunapakk (COD)|\n838|saturday / exchange / Lunapakk (COD)|\n840|1 day transit time|\n841|2 days transit time|\n842|3 days transit time|\n843|4 days transit time|\n844|5 days transit time|\n845|1 day transit time / Lunapakk (COD)|\n846|2 days transit time / Lunapakk (COD)|\n847|3 days transit time / Lunapakk (COD)|\n848|4 days transit time / Lunapakk (COD)|\n849|5 days transit time / Lunapakk (COD)|\n"
  },
  {
    "path": "pyshipping/carriers/dpd/georoutetables/SERVICEINFO.EN",
    "content": "#Filename: SERVICEINFO.EN\n#Version: 20140505\n#Expiration: 20140831\n#Hash: 85037002df19b84563bcfdbc1153cd85ff629e9f\n#Reference: http://extranet.dpd.de/georoute/references_dpd_20140505.txt\n#Fields: ServiceCode|ServiceFieldInfo|\n#Key: ServiceCode|\n102|hazardous goods|\n103|saturday|\n105|ex works|\n106|ex works / hazardous goods|\n109|C.O.D.|\n110|C.O.D. / hazardous goods|\n113|exchange|\n117|Collection-upon-Delivery|\n121|hazardous goods|\n124|ex works|\n125|ex works / hazardous goods|\n128|C.O.D.|\n129|C.O.D. / hazardous goods|\n132|exchange|\n137|saturday|\n138|ex works|\n140|C.O.D.|\n142|exchange|\n144|Collection-upon-Delivery|\n148|ex works|\n150|C.O.D.|\n152|exchange|\n154|DPD PARCELLetter|\n155|DPD GUARANTEE|\n158|DPD GUARANTEE ex works|\n161|DPD GUARANTEE C.O.D.|\n164|DPD GUARANTEE exchange|\n168|DPD GUARANTEE|\n171|DPD GUARANTEE ex works|\n174|DPD GUARANTEE C.O.D.|\n179|DPD 10:00|\n185|DPD 10:00 ex works|\n191|DPD 10:00 C.O.D.|\n197|DPD 10:00 exchange|\n203|DPD 10:00|\n209|DPD 10:00 ex works|\n215|DPD 10:00 C.O.D.|\n225|DPD 12:00|\n228|DPD 12:00 saturday|\n231|DPD 12:00 ex works|\n234|DPD 12:00 saturday / ex works|\n237|DPD 12:00 C.O.D.|\n240|DPD 12:00 saturday / C.O.D.|\n243|DPD 12:00 exchange|\n249|DPD 12:00|\n252|DPD 12:00 saturday|\n255|DPD 12:00 ex works|\n258|DPD 12:00 saturday / ex works|\n261|DPD 12:00 C.O.D.|\n264|DPD 12:00 saturday / C.O.D.|\n270|DPD 12:00 Saturday Swap Hazardous Goods|\n271|same day|\n294|DPD Mail|\n302|DPD EXPRESS|\n325|EXPRESS ECONOMY|\n326|EXPRESS ECONOMY|\n329|C.O.D.|\n330|C.O.D.|\n335|C.O.D.|\n336|C.O.D.|\n337|Parcel Shop Delivery|\n338|Parcel Shop Delivery|\n341|Parcel Shop Delivery C.O.D.|\n342|Parcel Shop Delivery C.O.D.|\n350|DPD 8:30|\n351|DPD 8:30 ex works|\n352|DPD 8:30 C.O.D.|\n353|DPD 8:30 exchange|\n354|DPD 8:30|\n355|DPD 8:30|\n356|DPD 8:30 ex works|\n357|DPD 8:30 C.O.D.|\n358|DPD 8:30 exchange|\n361|ex works|\n362|ex works|\n365|Tyre|\n366|Tyre B2C|\n367|Tyre C.O.D.|\n820|saturday|\n821|saturday|\n822|saturday / C.O.D.|\n823|saturday / C.O.D.|\n827|exchange|\n828|exchange|\n829|saturday|\n830|saturday|\n831|saturday / C.O.D.|\n832|saturday / C.O.D.|\n833|saturday / exchange|\n834|saturday / exchange|\n835|exchange / C.O.D.|\n836|exchange / C.O.D.|\n837|saturday / exchange / C.O.D.|\n838|saturday / exchange / C.O.D.|\n839|Evening Delivery|\n840|Evening Delivery|\n841|Evening Delivery COD|\n842|Evening Delivery COD|\n843|Special Pallet|\n844|Consumer Home|\n845|Consumer Home|\n846|Consumer Home & Shop|\n847|Consumer Home & Shop|\n848|4 days transit time / C.O.D.|\n849|5 days transit time / C.O.D.|\n"
  },
  {
    "path": "pyshipping/fortras/__init__.py",
    "content": "\"\"\"This module contains tools for reading and writing Fortras messages. Fortras is a EDI standard for\nlogistics related information somewhat common in Germany. See http://de.wikipedia.org/wiki/Fortras for further\nenlightenment.\"\"\"\n\n# You may consider this BSD licensed.\n\nfrom pyshipping.fortras.bordero import ship\n__all__ = [ship]\n"
  },
  {
    "path": "pyshipping/fortras/bordero.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n\"\"\"\nbordero.py - implements BORD messages. BORD is similar to IFTMIN in EDIFACT. Think of it as an work order\nto a freight forwarder.\n\nThis implementation is specific for usage between HUDORA GmbH and Mäuler but could be used as the basis for a\nmore generic implementation.\n\nCreated by Lars Ronge and Maximillian Dornseif on 2006-11-06.\nYou may consider this BSD licensed.\n\"\"\"\n\nimport os\nimport time\n\n\n#def _transcode(data):\n#    \"\"\"Decode utf-8 to latin1 (which is used by fortras).\"\"\"\n#    return data.decode('utf-8', 'replace').encode('latin-1', 'replace')\n\n\ndef _clip(length, data):\n    \"\"\"Clip a string to a maximum length.\"\"\"\n    # I wonder if this can't be done with clever formatstring usage.\n    if not isinstance(data, str):\n        data = data.decode('latin-1')\n    if len(data) > length:\n        return data[:length]\n    return data\n\n# zwei führende Nullen weg !!!\n# übertragung: \"übergepackt\"\n# übertragung: Palettenformat 1.5, 1, 0.5\n# überticragung: lieferfenster\n# DFü: Teil- Und Komplettladungen - verkehrsartschluessel : statt L (LKW) ein X (komplettladung)\n# Mäuler Telefonummer\n# Liste Auslandssendungen\n\n#doctext = \"\"\"Bordero-Kopf-Satz\"\"\"\n#Afelder = [\n#    dict(length=18, startpos=0,   endpos=19, name='borderonr',       fieldclass=IntegerFieldZeropadded),\n#    dict(length=8,  startpos=19,  endpos=27, name='datum',           fieldclass=DateFieldReverse,\n#         default=datetime.date.today()),\n#    dict(length=1,  startpos=27,  endpos=28,  name='versandweg',     fieldclass=RightAdjustedField,\n#         default='L', choices=('L', 'X')),\n#    dict(length=2,  startpos=28,  endpos=30,  name='fahrzeugnummer', fieldclass=IntegerFieldZeropadded),\n#    dict(length=10, startpos=34,  endpos=44,  name='versenderid',    fieldclass=RightAdjustedField,\n#          default='L515'),\n#    dict(length=13, startpos=44,  endpos=57,  name='frachtfuehrer',  fieldclass=RightAdjustedField,\n#          default='Maeuler'),\n#    dict(length=9,  startpos=53,  endpos=63,  name='plz',            fieldclass=RightAdjustedField,\n#          default='42897'),\n#    dict(length=12, startpos=63,  endpos=76,  name='ort',            fieldclass=RightAdjustedField,\n#          default='Remscheid'),\n#    dict(length=15, startpos=76,  endpos=91,  name='ladeeinheit1',   fieldclass=RightAdjustedField),\n#    dict(length=15, startpos=91,  endpos=106, name='ladeeinheit1',   fieldclass=RightAdjustedField),\n#    dict(length=10, startpos=106, endpos=117, name='plombe1',        fieldclass=RightAdjustedField),\n#    dict(length=10, startpos=117, endpos=127, name='plombe1',        fieldclass=RightAdjustedField),\n#    dict(length=10, startpos=117, endpos=127, name='release',        fieldclass=FixedField, default='6'),\n#]\n# Verkehrsartschlüssel (Satzart ‘A’, Position 031)\n# B = Bahn\n# E = Bordero für den eigenen Nahverkehr\n# H = VP an ZKP\n# K = Kombi\n# L = LKW\n# P = Packmittel-Clearing\n# W = Weiterleitungsbordero\n# X = Teilladungs-/Ladungspartie\n# Y = Bordero ZKP an EB ohne Weiterverladung\n# Z = ZKP an EP\n# C = Codis\n# N = Night Star Express\n# S = SystemPlus international\n# T = Teppichkurier\n\n\n#doctext=\"\"\"Versender-Adress-Satz - Teil 1\"\"\"\n#Bfelder = [\n#    dict(length=35, startpos= 0 , endpos= 35, name='versender_name1', default='HUDORA GmbH'),\n#    dict(length=35, startpos= 35, endpos= 70, name='versender_name2'),\n#    dict(length=35, startpos= 70, endpos=105, name='versender_strasse', default='Jägerwald 13'),\n#    dict(length= 3, startpos=105, endpos=107, name='versender_land', default='DE'),\n#    dict(length= 9, startpos=107, endpos=116, name='versender_plz', default='42897'),\n#    dict(length= 3, startpos=116, endpos=119, name='frei', fieldclass=FixedField, default='   '),\n#    dict(length= 3, startpos=119, endpos=122, name='codis_abholstelle', fieldclass=FixedField, default='   '),\n#    dict(length= 1, startpos=122, endpos=123, name='codis_laufkennzeichen',\n#         fieldclass=FixedField, default=' '),\n#]\n#\n#doctext=\"\"\"Versender-Adress-Satz - Teil 2\"\"\"\n#Cfelder = [\n#    dict(length=35, startpos=  0, endpos= 35, name='versenderort'),\n#    dict(length= 3, startpos= 35, endpos= 38, name='versender_berechnungsland', fieldclass=FixedField,\n#         default='   '),\n#    dict(length= 9, startpos= 38, endpos= 48, name='versender_berechnugnsplz', fieldclass=FixedField,\n#         default='         '),\n#    dict(length=35, startpos= 48, endpos= 83, name='versender_berechnungsort', fieldclass=FixedField,\n#         default='                                   '),\n#    dict(length=17, startpos= 83, endpos=100, name='versender_kundennummer', fieldclass=RightAdjustedField,\n#         default='11515'),\n#    dict(length= 9, startpos=100, endpos=109, name='warenwert', fieldclass=DecimalFieldWithoutDot, precision=2),\n#    dict(length= 3, startpos=109, endpos=112, name='waehrung', default='EUR'),\n#    dict(length=13, startpos=112, endpos=123, name='frei', fieldclass=FixedField, default='             '),\n#]\n#\n#doctext = \"\"\"Empfänger-Adress-Satz  - Teil 1\"\"\"\n#Dfelder = [\n#    dict(length=35, startpos=  0, endpos= 35, name='name1'),\n#    dict(length=35, startpos= 35, endpos= 70, name='name2'),\n#    dict(length=35, startpos= 70, endpos=105, name='stadtteil'),\n#    dict(length=19, startpos=105, endpos=123, name='frei'),\n#]\n#\n#doctext = \"\"\"Empfänger-Adress-Satz - Teil 2\"\"\"\n#Efelder = [\n#    dict(length=25, startpos=  0, endpos= 35, name='strasse'),\n#    dict(length= 3, startpos= 35, endpos= 38, name='land'),\n#    dict(length= 9, startpos= 38, endpos= 47, name='plz'),\n#    dict(length=35, startpos= 47, endpos= 81, name='Empfängerort muss'),\n#    dict(length= 3, startpos= 81, endpos= 84, name='Zustellbezirk Empfänger'),\n#    dict(length=10, startpos= 84, endpos= 94, name='Matchcode Empfänger- Nachname'),\n#    dict(length=17, startpos= 94, endpos=111, name='Kunden-Nr. Empfänger'),\n#    dict(length=10, startpos=111, endpos=121, name='Original-ID des VP beim  Empfangspartner*'),\n#    dict(length= 2, startpos=123, endpos=123, name='Frei'),\n#]\n#\n#dictext=\"\"\"Sendungs-Positions-Satz (je Sendungsteil; mehrfach möglich).\"\"\"\n#Ffelder = [\n#    dict(kength= 4, startpos=  0, endpos=  4, name='packstueck_anzahl'),\n#    dict(kength= 2, startpos=  4, endpos=  6, name='verpackungsart'), # ???\n#    dict(kength= 4, startpos=  6, endpos= 10, name='packstueck_anzahl_auf_paletten', fieldclass=FixedField,\n#         default='    '),\n#    dict(kength= 2, startpos= 10, endpos= 12, name='verpackungsart_auf_paletten', fieldclass=FixedField,\n#         default='  '),\n#    dict(kength=20, startpos= 10, endpos= 30, name='wareninhalt'),\n#    dict(kength=20, startpos= 30, endpos= 50, name='zeichen',\n#         doc=\"Zeichen der Sendung beim Versender\"),\n#    dict(kength= 5, startpos= 50, endpos= 55, name='gewicht', fieldclass=IntegerField,\n#         doc=\"Bruttogewicht in KG.\"),\n#    dict(kength= 5, startpos= 55, endpos= 60, name='gewicht_frachtpflichtig', fieldclass=IntegerField,\n#         doc=\"In der Regel max(gewicht, N), wobei N=150 oder 200\"),\n#    # Die Satzart ‘F’ darf nicht mit einem 2. Sendungsteil versehen werden,\n#    # wenn die Satzarten ‘G’ bzw. ‚Y‘ und ‚Z‘ (Gefahrgut) oder Satzart ‘H’ (Packstück-Nr.) folgen!\n#    dict(kength=68, startpos=  60, endpos=123, name='frei'),\n#]\n# Verpackungsartschlüssel (Satzart ‘F’, Positionen 009/010, 015/016, 071/072 und 077/078)\n# Schlüssel Art\n# AB Auf Bohlen\n# AD AD-Bahnbehälter\n# BD BD-Bahnbehälter\n# BE Beutel\n# BL Ballen\n# BU Bund\n# CC Collico\n# CO SystemPlus Collo\n# CD CD-Bahnbehälter\n# CP Chep-Palette\n# DO Dose\n# DR Drum\n# EI Eimer\n# EB Einweg-Behälter\n# EP Einweg-Palette\n# FA Fass\n# FK Faltkiste\n# FL Flasche\n# FP DB Euro-Flachpalette\n# GE Gebinde\n# GP Gitterboxpalette\n# GS Gestell\n# HC Haus-Haus-Corlette\n# HO Hobbock\n# HP Halbpalette\n# KA Kanne\n# KB Kundeneigener Sonderbehälter\n# KF Korbflasche\n# KI Kiste\n# KN Kanister\n# KO Korb\n# KP Kundeneigene Sonderpalette\n# KS Kasten\n# KT Karton\n# PA Paket\n# PK Pack\n# RC Rollcontainer\n# RG Ring\n# RO Rolle\n# SA Sack\n# SB Spediteureigener Behälter\n# ST Stück\n# TC Tankcontainer\n# TR Trommel\n# UV Unverpackt\n# VG Verschlag\n\n# 'H':  '002%(barcode)-35s%(foo)35s%(foo)35s%(foo)16s',\n# doctext = \"\"\"Packstück-Nummern-Satz (je Packstück-Nr./Gruppe; mehrfach möglich)\"\"\"\n#\n# BARCODETYP_CHOICES = { 1: 'Freie, unformatierte Markierung',\n#                        2: 'Nummer der Versandeinheit (EAN 128)',\n#                        3: 'Nummer der Versandeinheit (EAN 128) FORTRAS',\n#                        4: 'Paket-Nummer DPD (2/5 Interleaved)',\n#                        5: 'Router Label-Nummer DPD (2/5 Interleaved)',\n#                        6: 'Packstück-Nummer SystemPlus (2/5 Interleaved)',\n#                        7: 'Router Label-Nummer SystemPlus (2/5 Interleaved)',\n#                        8: 'IDS-Barcode (2/5 Interleaved)',\n#                        9: 'IDS-Barcode (39)',\n#                       10: 'IDS-Barcode (128)',\n#                       11: 'Nummer der Versandeinheit (Philips)',\n#                       12: 'Wechselbehälter-Barcode (2/5 Interleaved)',\n#                       13: 'DPD Container-Nummer (2/5 Interleaved)',\n#                       }\n# Hfelder = [\n#     dict(length= 3, startpos=  0, endpos=  3, name='barcodeart', fieldclass=IntegerFieldZerotagged,\n#          choices=BARCODETYP_CHOICES),\n#     dict(length=35, startpos=  3, endpos= 38, name='barcode1'),\n#     dict(length=35, startpos= 38, endpos= 73, name='barcode2'),\n#     dict(length=35, startpos= 73, endpos=108, name='barcode3'),\n#     dict(length=16, startpos=108, endpos=123, name='frei'),\n# ]\n#\n# # 'I': ('%(sendungsnummer)-16s%(sendungskilo)05d0000000000%(ladedm)03d    %(frankatur)-2s%(frankatur)-2s  %(foo)30s  %(foo)30s%(foo)16s  '),\n# doctext = \"\"\"Sendungs-Gewichte (je Sendung 1x)\"\"\"\n#\n# # Frankaturschlüssel des Spediteur-Übergabescheins (Satzart ‘I’, Positionen 043/044)\n# # kann Bordero-Frankatur wie folgt ergeben:\n# FRANKATUR_CHOICES = { 0: 'ohne Berechnung',\n#                       4: 'Frei Empfangsspediteur',\n#                       2: 'frei Haus',\n#                       5: 'frei Haus verzollt',\n#                       6: 'frei Haus unverzollt ',\n#                       7: 'unfrei',\n#                       9: 'Nachlieferung',\n#                       }\n#\n# # Hinweistextschlüssel (Satzart ‘I’, Positionen 047/048 und 079/080 sowie Satzart ‚T‘\n# # Positionen 002/003, 034/035 und 066/067)\n# # * Exakte Definition der Formate bei Schlüsseln mit Zusatztext:\n# #    1) Datum (TTMMJJJJ); Feldgröße: 8 Stellen\n# #    2) Uhrzeit (SSMM); Feldgröße: 4 Stellen\n# #    Die Positionen 049 - 056 enthalten das Datum (TTMMJJJJ), Position\n# #    057 ist leer, die Positionen 058 - 051 enthalten die Uhrzeit (SSMM).\n# HINWEISTEXTTYP_CHOICES = { 1: 'Sendung bitte avisieren unter Tel.-Nr.',\n#                            2: 'Recall-Service - Nach Zustellung Rückruf unter Tel.-Nr.',\n#                            3: 'Achtung Zollgut, Anlage Zollversandschein',\n#                           14: 'Termindienst! Auslieferung spätestens am (nicht Eingangstag)',\n#                           15: 'Fixtermin! Nicht früher oder später - am',\n#                           16: 'Termingut! Unbedingt zustellen in KW:',\n#                           17: 'SystemPlus 10-Uhr-Service - Zustellung bis 10.00 Uhr',\n#                           18: 'SystemPlus 12-Uhr-Service - Zustellung bis 12.00 Uhr',\n#                           19: 'SystemPlus Next-Day-Service (24 Std.-Service)',\n#                           20: 'SystemPlus international Express Service',\n#                           21: 'Sendung bitte nur liegend transportieren',\n#                           22: 'Thermogut - vorgegebenen Temperaturbereich beachten',\n#                           23: 'Empfänger kann Regalservice verlangen',\n#                           25: 'Samstag-Service via Night Star Express',\n#                           50: 'Warenwertnachnahme - Quick-Nachnahme',\n#                           51: 'Zustellung unbedingt mit Hebebühnen-LKW',\n#                           52: 'Sendung vor Zustellung telefonisch avisieren',\n#                           53: 'Achtung Warenwertnachnahme - nur gegen bar ausliefern',\n#                           58: 'Warenwertnachnahme - Verrechnungsscheck',\n#                           60: 'Sendung ohne Entladung direkt ausliefern',\n#                           61: 'Empfindliche Ware - vorsichtig behandeln',\n#                           70: 'Lieferscheinnummer',\n#                           71: 'Kundenauftragsnummer',\n#                           72: 'Abholauftragsreferenz',\n#                           73: 'Freie weitere Kundenreferenz',\n#                           74: 'Retourenreferenz',\n#                           }\n#\n#\n# # Hinweistextschlüssel der Auftragsart (Satzart ‘I’, Position 127)\n# # 'I': ('%(sendungsnummer)-16s%(sendungskilo)05d0000000000%(ladedm)03d    %(frankatur)-2s%(frankatur)-2s  %(foo)30s  %(foo)30s%(foo)16s  '),\n# Ifelder = [\n#     dict(lentgth=16, startpos=  0, endpos= 16, name='Sendungs-Nr. Versandpartner****** muss'),\n#     dict(lentgth= 5, startpos= 16, endpos= 21, name='gewicht', fieldclass=IntegerField,\n#          doc=\"Sendungsgewicht in KG\"),\n#     dict(lentgth= 5, startpos= 21, endpos= 26, name='gewicht_frachtpflichtig', fieldclass=IntegerField,\n#          doc=\"Frachtpflichtiges Sendungsgewicht in KG\"),\n#     dict(lentgth= 5, startpos= 26, endpos= 31, name='kubikdezimeter', fieldclass=IntegerField),\n#     dict(lentgth= 3, startpos=  0, endpos=123, name='lademeter', fieldclass=DecimalFieldWithoutDot,\n#          precision=1),\n#     dict(lentgth= 2, startpos=  0, endpos=123, name='zusaetzliche_ladehilfsmittel', fieldclass=IntegerField),\n#     dict(lentgth= 2, startpos=  0, endpos=123, name='verpackungsart_zusaetzliche_ladehilfsmittel'), # 2 041 - 042\n#     dict(lentgth= 2, startpos=  0, endpos=123, name='frankatur_fpediteur-Über- gabeschein* muss', choices=FRANKATUR_CHOICES), # 2 043 - 044\n#     dict(lentgth= 2, startpos=  0, endpos=123, name='Frankatur Bordero* muss', choices=FRANKATUR_CHOICES), # 2 045 - 046\n#     dict(lentgth= 2, startpos=  0, endpos=123, name='Hinweistextschlüssel 1** kann'), # 2 047 - 048\n#     dict(lentgth=30, startpos=  0, endpos=123, name='Hinweiszusatztext 1 kann'), # 30 049 - 078\n#     dict(lentgth= 2, startpos=  0, endpos=123, name='Hinweistextschlüssel 2** kann'), # 2 079 - 080\n#     dict(lentgth=30, startpos=  0, endpos=123, name='Hinweiszusatztext 2 kann'), # 30 081 - 110\n#     dict(lentgth=16, startpos=  0, endpos=123, name='Sendungs-Nr.  Empfangspartner*** kann'),  # 16 111 - 126\n#     dict(lentgth= 1, startpos=121, endpos=122, name='auftragsart**** kann'), # 1 127 - 127\n#     dict(lentgth= 1, startpos=122, endpos=123, name='lieferscheindaten_folgen', fieldclass=FixedField,\n#          default=' '),\n# ]\n#** siehe beigefügter Hinweistextschlüssel\n#*** bei Nachlieferungen (Original-Sendungs-Nr. des EP) bzw. bei Ersterfassung von über-\n#zähligen Sendungen (vorläufige Sendungs-Nr. des EP)\n#**** siehe beigefügter Schlüsseltabelle für Auftragsarten\n\n\n# 'L': ('%(sendungen)05d%(packstuecke)05d%(bruttogewicht)05d'\n#       + '%(kostensteuerplichtig)09d%(kostensteuerfrei)09d000000000%(kostenzoll)09d'\n#       + '%(eust)09d000%(gitterboxen)03d%(europaletten)03d000000000000'\n#       + '%(sonstigeladehilfsmittel)03d000N%(foo)36s'),\n#                                                (Muss/Kann)              davon dezimal                      Position\n# Bordero-Summen-Satz       muss\n# (je Bordero 1x)\n# Satzart ‘L’ muss 1 001 - 001\n# Konstante ‘999’ muss 3/0 002 - 004\n# Gesamt-Sendungs-Anzahl muss 5/0 005 - 009\n# Gesamt-Packstück-Anzahl muss 5/0 010 - 014\n# Tatsächliches Bruttoge-\n# wicht gesamt in kg muss 5/0 015 - 019\n# Gesamt-Empf.-Kosten\n# steuerpflichtig muss 9/2 020 - 028\n# Gesamt-Empf.-Kosten\n# steuerfrei muss 9/2 029 - 037\n# Gesamt-Versendernach-\n# nahme muss 9/2 038 - 046\n# Gesamt-Zoll muss 9/2 047 - 055\n# Gesamt-EUSt muss 9/2 056 - 064\n# Anzahl SB = spediteur-\n# eigene Behälter muss 3/0 065 - 067\n# Anzahl GP = Gitterbox-\n# Paletten** muss 3/0 068 - 070\n# Anzahl FP = Euro-Flach-\n# Paletten** muss 3/0 071 - 073\n# Anzahl CC = Collico muss 3/0 074 - 076\n# Anzahl AD = Bahnbehälter muss 3/0 077 - 079\n# Anzahl BD = Bahnbehälter muss 3/0 080 - 082\n# Anzahl CD = Bahnbehälter muss 3/0 083 - 085\n# Anzahl FP = zusätzliche\n# Ladehilfsmittel ** muss 3/0 086 - 088\n# Anzahl GP = zusätzliche\n# Ladehilfsmittel** muss 3/0 089 - 091\n# Clearing-Kennzeichen (J/N)*muss 1 092 - 092\n# Frei  36 093 - 128\n# Die Summenfelder für Frachtbeträge, Kosten und Nachnahmen innerhalb dieser Satzart sind\n# währungsneutral, d.h. es ist lediglich eine Summierung der Werte aus der Satzart K vorzunehmen.\n# Eine Währungsumrechnung darf nicht erfolgen.\n# * J  = Kosten sind für das Clearing zu berücksichtigen (wird nicht verwendet)\n# N = Kosten sind nicht für das Clearing zu berücksichtigen (wird nicht verwendet)\n#\n#\n# 'T': ('%(textschluessel1)02s%(hinweistext1)-30s'\n#       + '%(textschluessel2)02s%(hinweistext2)-30s'\n#       + '%(textschluessel3)02s%(hinweistext3)-30s%(foo)28s'),\n# Textschlüssel-Satz*                        kann\n# (je Sendung maximal 3x)\n# Satzart ‘T’ muss 1 001 - 001\n# Laufende Bordero-Position muss 3/0 002 - 004\n# Hinweistextschlüssel 1** kann 2 005 - 006\n# Hinweiszusatztext 1 kann 30 007 - 036\n# Hinweistextschlüssel 2** kann 2 037 - 038\n# Hinweiszusatztext 2 kann 30 039 - 068\n# Hinweistextschlüssel 3** kann 2 069 - 070\n# Hinweiszusatztext 3 kann 30 071 - 100\n# Frei\n#\n# 'J':  '%(zusatztext1)-62s%(zusatztext2)-62s',\n# Satzart ‘J’ muss 1 001 - 001\n# Laufende Bordero-Position muss 3/0 002 - 004\n# Zusatztext 1 muss 62 005 - 066\n# Zusatztext 2 kann 62 067 - 128\n\nclass Bordero(object):\n    \"\"\"Kapselt die Daten für ein Gefäß (LKW) - Verwendung ähnlich IFTSTAR.\"\"\"\n\n    def __init__(self, empfangspartner='11515'):\n        super(Bordero, self).__init__()\n        self.filename = \"\"\n        self.empfangspartner = empfangspartner\n        self.lieferungen = []\n        self.satznummer = 0\n        self.borderonr = None\n        self.verladung = None\n        self.generated_output = ''\n        # definitions of records\n        self.satzarten = {'A': (u'%(borderonr)018d%(datum)-8s%(versandweg)s  %(empfangspartner)-10s %(frachtfuehrer)'\n                                + '-13s%(plz)-9s%(ort)-12s%(foo)-49s6'),\n                          'B': u'%(name1)-35s%(name2)-35s%(strasse)-35s%(lkz)-3s%(plz)-9s       ',\n                          'C': u'%(ort)-35s%(foo)-3s%(foo)9s%(foo)35s%(kdnnr)17s%(wert)09fEUR%(foo)-13s',\n                          'D': u'%(name1)-35s%(name2)-35s%(foo)-35s%(foo)-19s',\n                          'E': (u'%(strasse)-35s%(lkz)-3s%(plz)-9s%(ort)-35s%(foo)3s%(matchcode)-10s'\n                                + '%(kdnnr)17s%(foo)10s%(foo)2s'),\n                          'F': (u'%(anzahlpackstuecke)04d%(verpackungsart)2s0000  %(wareninhalt)-20s'\n                                + '%(zeichennr)-20s%(sendungskilo)05d00000%(foo)-62s'),\n                          'H': '002%(barcode)-35s%(foo)35s%(foo)35s%(foo)16s',\n                          'I': (u'%(sendungsnummer)-16s%(sendungskilo)05d0000000000%(ladedm)03d    '\n                                + '%(frankatur)-2s%(frankatur)-2s  %(foo)30s  %(foo)30s%(foo)16s  '),\n                          'L': (u'%(sendungen)05d%(packstuecke)05d%(bruttogewicht)05d'\n                                + '%(kostensteuerplichtig)09d%(kostensteuerfrei)09d000000000%(kostenzoll)09d'\n                                + '%(eust)09d000%(gitterboxen)03d%(europaletten)03d000000000000'\n                                + '%(sonstigeladehilfsmittel)03d000N%(foo)36s'),\n                          'T': (u'%(textschluessel1)02s%(hinweistext1)-30s'\n                                + '%(textschluessel2)02s%(hinweistext2)-30s'\n                                + '%(textschluessel3)02s%(hinweistext3)-30s%(foo)28s'),\n                          'J': u'%(zusatztext1)-62s%(zusatztext2)-62s',\n                          }\n\n    def add_lieferung(self, lieferung):\n        \"\"\"Adds a lieferung to our Bordero.\"\"\"\n        if self.generated_output:\n            raise RuntimeError('tried to  add data to an already exported Bordero.')\n        self.lieferungen.append(lieferung)\n\n    def generate_satz(self, satzart, data):\n        \"\"\"Helper function to generate output for a Record.\"\"\"\n        for key in data.keys():\n            if isinstance(data[key], str):\n                data[key] = unicode(data[key], 'ascii', errors='ignore')\n        ret = ((u'%s%03d' % (satzart, self.satznummer)) +\n               (self.satzarten[satzart] % data))\n        if len(ret) != 128:\n            raise RuntimeError('bordero Satz %r kaputt! (len=%r) %r %r' % (satzart, len(ret), ret, data))\n        return ret\n\n    def generate_kopfsatz_a(self, verladung=None):\n        \"\"\"Generates bodero record A - header.\"\"\"\n        # Bordro nummer - Fortlaufend?\n        # Verkehrsart - immer LKW?\n        # IDNr des Verrsandpartyners beim Empfangspartern? L515\n        # Frachtführername - Mäuler?\n        # Ladeeinheitnr - 1 & 2? frei\n        # Plombennummer ?        frei\n        if not self.borderonr:\n            raise RuntimeError('No bordero nr set.')\n        data = {'empfangspartner': self.empfangspartner, 'frachtfuehrer': 'Maeuler', 'plz': '42897',\n                'ort': 'Remscheid', 'versandweg': 'L', 'foo': '',\n                'datum': time.strftime('%d%m%Y'),\n                'borderonr': self.borderonr}\n        if verladung:\n            if verladung.spedition == 'Direktfahrt':\n                data['versandweg'] = 'X'\n        return self.generate_satz('A', data)\n\n    def generate_versendersatz_b(self, lieferung):\n        \"\"\"Generates bodero record B - first half of sender.\"\"\"\n        data = {'name1': 'HUDORA GmbH', 'name2': '', 'strasse': u'Jägerwald 13', 'lkz': 'DE',\n                'plz': '42897'}\n        return self.generate_satz('B', data)\n\n    def generate_versendersatz_c(self, lieferung):\n        \"\"\"Generates bodero record C - second half of sender.\"\"\"\n        data = {'ort': 'Remscheid', 'foo': ' ', 'kdnnr': self.empfangspartner, 'wert': 0}\n        return self.generate_satz('C', data)\n\n    def generate_empfaengersatz_d(self, lieferung):\n        \"\"\"Generates bodero record D - first half of recipient.\"\"\"\n        data = {'name1': _clip(35, lieferung.name1), 'name2': _clip(35, lieferung.name2), 'foo': ' '}\n        return self.generate_satz('D', data)\n\n    def generate_empfaengersatz_e(self, lieferung):\n        \"\"\"Generates bodero record E - second half of recipient.\"\"\"\n        data = {'strasse': _clip(35, lieferung.adresse), 'lkz': _clip(3, lieferung.land),\n        'plz': _clip(9, lieferung.plz), 'ort': _clip(35, lieferung.ort), 'kdnnr': lieferung.kundennummer,\n        'matchcode': _clip(10, (lieferung.name1 + lieferung.ort).replace(' ', '')), 'foo': ' '}\n        return self.generate_satz('E', data)\n\n    def generate_sendungspossatz_f(self, lieferung):\n        \"\"\"Generates bodero record F.\"\"\"\n        # Packstück Anzahl? Pakete? Paletten? NVEs!\n        # Fuer weitere Paletten typen neuen Satz\n        # Tatsächliches Gewicht einpflegen\n        data = {'anzahlpackstuecke': len(lieferung.packstuecke),\n        'verpackungsart': _clip(2, 'FP'),\n        'sendungskilo': int(lieferung.gewicht / 1000),\n        'wareninhalt': _clip(20, 'HUDORA Sportartikel'),\n        'zeichennr': _clip(20, '%s/%s' % (lieferung.lieferscheinnummer, lieferung.id)),\n        'foo': ''}\n        return self.generate_satz('F', data)\n\n    def generate_packstuecksatz(self, lieferung, packstueck):\n        \"\"\"Generates bodero record H.\"\"\"\n        barcode = packstueck.nve\n        if not barcode.startswith('00'):\n            barcode = '00' + barcode\n        data = {'barcode': barcode,\n        'foo': ' '}\n        return self.generate_satz('H', data)\n\n    def generate_sendungsinfosatz_i(self, lieferung):\n        \"\"\"Generates bodero record I.\"\"\"\n        # Sendungsnummer - eindeutig!\n        data = {'sendungsnummer': _clip(16, \"%016s\" % lieferung.id),\n        'ladedm': 0,\n        'frankatur': '02',  # frei Haus\n        'foo': ' '}\n        data['sendungskilo'] = int(lieferung.gewicht / 1000)\n        return self.generate_satz('I', data)\n\n    def generate_textsatz_t(self, lieferung, schluesselliste):\n        \"\"\"Generates bodero record(s) T - text info.\"\"\"\n        satz = []\n        ret = []\n        for schluessel, text in schluesselliste:\n            satz.append((schluessel, text))\n            if len(satz) == 3:\n                data = {'textschluessel1': '%02d' % int(satz[0][0]), 'hinweistext1': _clip(30, satz[0][1]),\n                        'textschluessel2': '%02d' % int(satz[1][0]), 'hinweistext2': _clip(30, satz[1][1]),\n                        'textschluessel3': '%02d' % int(satz[2][0]), 'hinweistext3': _clip(30, satz[2][1]),\n                        'foo': ' '}\n                satz = []\n                ret.append(self.generate_satz('T', data))\n        if len(satz) > 0:\n            data = {'textschluessel1': '%02s' % int(satz[0][0]), 'hinweistext1': _clip(30, satz[0][1]),\n                    'textschluessel2': '  ', 'hinweistext2': '',\n                    'textschluessel3': '  ', 'hinweistext3': '',\n                    'foo': ' '}\n            if len(satz) > 1:\n                data['textschluessel2'] = '%02d' % int(satz[1][0])\n                data['hinweistext2'] = _clip(30, satz[1][1])\n            ret.append(self.generate_satz('T', data))\n        return '\\n'.join(ret)\n\n    def generate_textsaetze(self, lieferung):\n        \"\"\"Generate as many T records as needed.\"\"\"\n        schluesselliste = []\n        if lieferung.avisieren_unter:\n            # 52 = Sendung vor Zustellung telefonisch avisieren\n            # 01 = Sendung bitte avisieren unter Tel.-Nr.: ...(Zusatztext)\n            schluesselliste.append(('01', _clip(30, lieferung.avisieren_unter)))\n        if lieferung.fixtermin:\n            # 15 = Fixtermin! Nicht früher oder später - am: ... (Zusatztext)*\n            datum = lieferung.fixtermin.strftime('%d%m%Y')\n            zeit = lieferung.fixtermin.strftime('%H:%M')\n            if zeit == '00:00':\n                zeit = '     '\n            timestamp = '%8s %5s' % (datum, zeit)\n            schluesselliste.append(('15', _clip(30, timestamp)))\n        if lieferung.hebebuehne:\n            # 51 = Zustellung unbedingt mit Hebebühnen-LKW\n            schluesselliste.append(('51', _clip(30, 'Zustellung mit Hebebühne')))\n        if lieferung.auftragsnummer_kunde.strip():\n            # 71 = Kundenauftragsnummer (Nummer im Zusatztext)\n            schluesselliste.append(('71', _clip(30, lieferung.auftragsnummer_kunde)))\n        # 70 = Lieferscheinnummer (Nummer im Zusatztext)\n        schluesselliste.append(('70', _clip(30, lieferung.lieferscheinnummer)))\n        # 73 = Freie weitere Kundenreferenz (Referenz im Zusatztext)\n        # schluesselliste.append(('73', _clip(30, 'http://hudora.de/track/XXX')))\n\n        # 14 = Termindienst! Auslieferung spätestens am (nicht Eingangstag): (Zusatztext)*\n        # 16 = Termingut! Unbedingt zustellen in KW: ... (Zusatztext)\n        # 61 = Empfindliche Ware - vorsichtig behandeln\n        return self.generate_textsatz_t(lieferung, schluesselliste)\n\n    def generate_zusatztextsatz_j(self, lieferung):\n        \"\"\"Generates bodero record J - additional text info.\"\"\"\n        data = {'zusatztext1': _clip(62, u'AuftragsNr: %s / KundenNr: %s' %\n                                         (lieferung.auftragsnummer, lieferung.kundennummer)),\n                'zusatztext2': _clip(62, u'huLOG Code: %s' % lieferung.code),\n                'foo': ' '}\n        return self.generate_satz('J', data)\n\n    def generate_summensatz_l(self):\n        \"\"\"Generates bodero record L.\"\"\"\n        # Was ist ein Packstück? => NVEs\n        # Empfangskosten?\n        # Was bei Einwegpaletten?\n        # Was bei CHEP / Düsseldorfer Paletten = speditereigenebehaelter\n        # clearing kennzeichen?\n        data = {'sendungen': len(self.lieferungen),\n            'bruttogewicht': int(sum([lieferung.gewicht for lieferung in self.lieferungen]) / 1000),\n            'kostensteuerplichtig': 0,\n            'kostensteuerfrei': 0,\n            'kostenzoll': 0,\n            'eust': 0,\n            'gitterboxen': 0,\n            'europaletten': sum([len(lieferung.packstuecke) for lieferung in self.lieferungen]),\n            'packstuecke': sum([len(lieferung.packstuecke) for lieferung in self.lieferungen]),\n            'sonstigeladehilfsmittel': 0,\n            'foo': '',\n        }\n        self.satznummer = 999\n        return self.generate_satz('L', data)\n\n    def generate_lieferungssaetze(self, lieferung):\n        \"\"\"Generates all bodero records for a shipment.\"\"\"\n        self.satznummer += 1\n        out = []\n        out.append(self.generate_versendersatz_b(lieferung))\n        out.append(self.generate_versendersatz_c(lieferung))\n        out.append(self.generate_empfaengersatz_d(lieferung))\n        out.append(self.generate_empfaengersatz_e(lieferung))\n        out.append(self.generate_sendungspossatz_f(lieferung))\n        for packstueck in lieferung.packstuecke:\n            out.append(self.generate_packstuecksatz(lieferung, packstueck))\n        out.append(self.generate_sendungsinfosatz_i(lieferung))\n        out.append(self.generate_textsaetze(lieferung))\n        out.append(self.generate_zusatztextsatz_j(lieferung))\n        return u'\\n'.join(out)\n\n    def generate_dataexport(self):\n        \"\"\"Return complete BODERO data.\"\"\"\n        if not self.generated_output:\n            out = []\n            out.append(self.generate_kopfsatz_a(self.verladung))\n            for lieferung in self.lieferungen:\n                out.append(self.generate_lieferungssaetze(lieferung))\n            out.append(self.generate_summensatz_l())\n            self.generated_output = (u\"@@PHBORD128 0128003500107 HUDORA1 MAEULER\\n\"\n                                     + u'\\n'.join(out) + u'\\n@@PT\\n')\n        return self.generated_output\n\n\ndef ship(verladung, empfangspartner='11515', basedir='/usr/local/maeuler/current/In/BORD/'):\n    \"\"\"Creates a BORDERO object and writes it to a file.\n\n    The file lies in directory basedir containing the borderonr and a timestamp in its name.\n    Returns the BORDERO object w/ its filename attached.\n    \"\"\"\n    bordero = Bordero(empfangspartner)\n    bordero.verladung = verladung\n    bordero.borderonr = verladung.borderonr\n    for lieferung in verladung.lieferungen:\n        bordero.add_lieferung(lieferung)\n    data = bordero.generate_dataexport()  # generate first to assure bordero.borderonr is set\n    # we first create a temporary file and later rename it to it's final name\n    basefilename = time.strftime('%Y%m%dT%H%M%S') + ('_%05d.txt' % (bordero.borderonr))\n    tmpfilename = os.path.join(basedir, '._' + basefilename + '_tmp')\n    filename = os.path.join(basedir, basefilename)\n    outfile = open(tmpfilename, 'w')\n    outfile.write(data.encode('latin-1', 'ignore'))\n    outfile.close()\n    os.rename(tmpfilename, filename)\n    for lieferung in verladung.lieferungen:\n        lieferung.ship()\n        lieferung.log(code=201, message=\"Verladung abgeschlossen / DFü an Spedition (Mäuler)\")\n        lieferung.save()\n    verladung.ship()\n    bordero.filename = filename\n    return bordero\n\n\ndef ship_lieferungen(lieferungen, empfangspartner='11515'):\n    \"\"\"Should be called when 'lieferungen' just left the building.\"\"\"\n    bordero = Bordero(empfangspartner)\n    for lieferung in lieferungen:\n        bordero.add_lieferung(lieferung)\n    data = bordero.generate_dataexport()  # generate first to assure bordero.borderonr is set\n    # we first create a temporary file and later rename it to it's finaal name\n    filename = time.strftime('%Y%m%dT%H%M%S') + ('_%05d.txt' % (bordero.borderonr))\n    outfile = open(os.path.join('/usr/local/maeuler/current/In/BORD/', '._' + filename + '_tmp'), 'w')\n    outfile.write(data)\n    outfile.close()\n    os.rename(os.path.join('/usr/local/maeuler/current/In/BORD/', '._' + filename + '_tmp'),\n              os.path.join('/usr/local/maeuler/current/In/BORD/', filename))\n    for lieferung in lieferungen:\n        lieferung.ship()\n        lieferung.log(code=201, message=\"DFü an Mäuler\")\n        lieferung.save()\n    return bordero\n"
  },
  {
    "path": "pyshipping/fortras/entl.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n\"\"\"\nentl.py - parse Fortras ENTL messages.\n\nCreated by Maximillian Dornseif on 2006-11-19.\nYou may consider this BSD licensed.\n\"\"\"\n\nimport re\nimport logging\nimport datetime\n\n\nclass Entladebericht(object):\n    \"\"\"Parses and represent an ENTL message.\"\"\"\n    m_record_re = (r'M(?P<borderonr>[0-9 ]{18})(?P<borderodatum>[0-9 ]{8})(?P<kundennummer>.{10})'\n                   + r'(?P<eingangsdatum>[0-9 ]{8})(?P<eingangszeit>[0-9 ]{4})(?P<zeitschranfenstatus>..)'\n                   + r'(?P<entladebeginn_datum>[0-9 ]{8})(?P<entladebeginn_zeit>[0-9 ]{4})'\n                   + r'(?P<entladeende_datum>[0-9 ]{8})(?P<entladeende_zeit>[0-9 ]{4})'\n                   + r'(?P<entladehinweis>.{30})             (?P<foo>.{9})5')\n    m_record_re = re.compile(m_record_re)\n    # Satzart ‘M’ muss 1 001 - 001\n    # Bordero-Nr.Versandpartner muss          18 002 - 019\n    # Borderodatum Versandpartner (TTMMJJJJ) muss 8/0 020 - 027\n    # Kunden-Nr. Empfangspartner beim Versandpartner muss          10 028 - 037\n    # Eingangsdatum (TTMMJJJJ) muss 8/0 038 - 045\n    # Eingangszeit (SSMM) muss 4/0 046 - 049\n    # Zeitschranken-Status* muss 2 050 - 051\n    # Entladebeginn-Datum (TTMMJJJJ) muss 8/0 052 - 059\n    # Entladebeginn Zeit (SSMM) muss 4/0 060 - 063\n    # Entladeende-Datum (TTMMJJJJ) muss 8/0 064 - 071\n    # Entladeende-Zeit (SSMM) muss 4/0 072 - 075\n    # Nicht sendungsbezogener Entladehinweis-Text kann          30 076 - 105\n    # Euro-Paletten-Belastung  Versandpartner aus Bordero kann 3/0 106 - 108\n    # Gitterbox-Paletten-  Belastung Versandpartner  aus Bordero kann 3/0 109 - 111\n    # Euro-Paletten-Entladung Empfangspartner kann 3/0 112 - 114\n    # Gitterbox-Paletten-Entladung Empfangspartner kann 3/0 115 - 117\n    # Verkehrsart** kann 1 118 - 118\n    # Frei 9 119 - 127\n    # Releasestand \u00015’ muss 1 128 - 128\n\n    n_record_re = (r'N(?P<borderonr>[0-9 ]{18})(?P<position>[0-9 ]{3})(?P<sendungsnrversender>[0-9 ]{16})'\n                   + r'(?P<sendungsnrempfaenger>[0-9 ]{16})(?P<differenzschluessel1>..)'\n                   + r'(?P<differenzzahl1>[0-9 ]{4})(?P<verpackungsart1>..)(?P<differenztext1>.{29})'\n                   + r'(?P<differenzschluessel2>..)(?P<differenzzahl2>[0-9 ]{4})(?P<verpackungsart2>..)'\n                   + r'(?P<differenztext2>.{29})')\n    n_record_re = re.compile(n_record_re)\n    # Satzart ‘N’ muss 1 001 - 001\n    # Bordero-Nr. Versandpartner muss          18 002 - 019\n    # Laufende Bordero-Position Versandpartner muss 3/0 020 - 022\n    # Sendungs-Nr.  Versandpartner muss          16 023 - 038\n    # Sendungs-Nr. Empfangspartner muss          16 039 - 054\n    # Differenzartschlussel 1* muss 2 055 - 056\n    # Differenzanzahl 1 kann 4/0 057 - 060\n    # Verpackungsart 1** kann 2 061 - 062\n    # Text/Hinweis 1 kann          29 063 - 091\n    # Differenzartschlussel 2* kann 2 092 - 093\n    # Differenzanzahl 2 kann 4/0 094 - 097\n    # Verpackungsart 2** kann 2 098 - 099\n    # Text/Hinweis 2 kann          29 100 - 128\n\n    # no match 'V461               720-00             00340498430009431112               0                          19092008183100              '\n    v_record_re = (r'V(?P<borderonr>[0-9 ]{18})(?P<sendungsnrversender>.{16})(?P<barcodetype>...)'\n                   + r'(?P<nve>.{35})(?P<hinweiscode>...)(?P<hinweistext>.{24})'\n                   + r'(?P<date>[0-9 ]{8})(?P<time>[0-9 ]{4})'\n                   + r'(?P<benutzer>.{10})(?P<terminal>.{4})')\n    v_record_re = re.compile(v_record_re)\n    # Lost 'N'-Satz ab bei Einsatz von Barcode\n    # Satzart ‘V’ muss 1 001 - 001\n    # Bordero-Nr. Versandpartner muss          18 002 - 019\n    # Sendungs-Nr. Versandpartner muss          16 020 - 035\n    # Barcode-Qualifier * kann 3 036 - 038\n    # Barcode-Nr. muss          35 039 - 073\n    # Fehler-/Hinweiscode 1 ** muss 3 074 - 076\n    # Frei wahlbarer Text/Hinweis kann          24 077 - 100\n    # Ereignisdatum (TTMMJJJJ) muss 8 101 - 108\n    # Ereignisuhrzeit (HHMMSS) muss 6 109 - 114\n    # Benutzer-ID kann          10 115 - 124\n    # Scanner/Terminal-ID kann 4 125 - 128\n    # Fehler-/Hinweiscode:\n    statustexte = {\n        0: 'Packstck',\n        2: 'Packstck eingedrckt',\n        3: 'Packstck aufgerissen, Ware greifbar',\n        4: 'Packstck nass',\n        5: 'Packstckinhalt luft aus',\n        9: 'Packstck beschdigt',\n       10: 'Euro-Palette',\n       11: 'Euro-Palette, Ware beschdigt',\n       12: 'Euro-Palette beschdigt',\n       13: 'Euro-Palette und Ware beschdigt',\n       14: 'Euro-Palette verschweit/gewickelt',\n       15: 'Euro-Palette, Folie ein-/aufgerissen, Ware greifbar',\n       19: 'Euro-Palette und/oder Ware mit Beschdigung',\n       20: 'Gitterbox',\n       21: 'Gitterbox, Ware beschdigt',\n       22: 'Gitterbox beschdigt',\n       23: 'Gitterbox und Ware beschdigt',\n       29: 'Gitterbox und/oder Ware mit Beschdigung',\n       30: 'Halbpalette',\n       31: 'Halbpalette, Ware beschdigt',\n       32: 'Halbpalette beschdigt',\n       33: 'Halbpalette und Ware beschdigt',\n       34: 'Halbpalette verschweit/gewickelt',\n       35: 'Halbpalette, Folie ein-/aufgerissen, Ware greifbar',\n       39: 'Halbpalette und/oder Ware mit Beschdigung',\n       40: 'Einwegpalette',\n       41: 'Einwegpalette, Ware beschdigt',\n       42: 'Einwegpalette beschdigt',\n       43: 'Einwegpalette und Ware beschdigt',\n       44: 'Einwegpalette verschweit/gewickelt',\n       45: 'Einwegpalette, Folie ein-/aufgerissen, Ware greifbar',\n       49: 'Einwegpalette und/oder Ware mit Beschdigung.',\n       50: 'Packstck fehlt bei Entladung',  # (Wird durch Entladebericht beim EP erzeugt)\n       51: 'Backbox SK1*',\n       52: 'Backbox SK2*',\n       53: 'Backbox SK3*',\n       54: 'Backbox SK4*',\n       62: 'HUB/Konsolidierungspunkt - Packstck handverteilt',\n       63: 'HUB/Konsolidierungspunkt - Palette mit berma',\n       64: 'HUB/Konsolidierungspunkt - Packstuckroutung nicht lesbar (Nachbearbeitung)',\n       65: 'HUB/Konsolidierungspunkt - Versender Direktanlieferung - Aufkleber erstellt',\n       66: 'HUB/Konsolidierungspunkt - Dienstleistung - Routung durch HUB / Konsolidierungspunkt',\n       67: 'HUB/Konsolidierungspunkt - Dienstleistung - Vereinbarte Nachbearbeitung',\n       68: 'HUB/Konsolidierungspunkt - Retoure HUB / Konsolidierungspunkt an Versandspediteur',\n       69: 'HUB/Konsolidierungspunkt - Ware im HUB / Konsolidierungspunkt stehen geblieben',\n       70: 'HUB/Konsolidierungspunkt - Ware im HUB palettiert',\n       93: 'Routerfehler',\n       94: 'Verladefehler',\n       95: 'PLZ-Fehler*',\n       96: 'Platzmangel',\n       97: 'Zeitmangel',\n       98: 'Rckscannung - Packstuck wieder entladen',\n       99: 'Korrekturscannung',\n       }\n\n    def update_packstueck(self, nve, datadict):\n        \"\"\"Updates a huLOG Packstueck record with the parsed ENTL data.\"\"\"\n\n        # TODO: decouple this from huLOG\n        import huLOG.models\n        try:\n            packstueck = huLOG.models.Packstueck.objects.get(_trackingnummer=nve)\n        except huLOG.models.Packstueck.DoesNotExist:\n            logging.warning('Problem locating Packstueck with barcode %r for ENTL record - ignoring' % nve)\n            return\n        info = []\n        if datadict['terminal']:\n            info.append('an Terminal %r' % datadict['terminal'])\n        if datadict['benutzer']:\n            info.append('gescannt durch Nutzer %r' % datadict['benutzer'])\n        if datadict['hinweiscode'] in ['0', '']:\n            info.insert(0, 'Bei der Spedition entladen')\n            if datadict['hinweistext']:\n                info.insert(1, datadict['hinweistext'])\n            if datadict['timestamp']:\n                info.append(str(datadict['timestamp']))\n            log = huLOG.models.PackstueckLogentry(packstueck=packstueck)\n            log.displaytext = ', '.join(info)\n            log.sourcedata = repr(datadict)\n            log.source = 'Maeuler ENTL'\n            log.code = '200'\n            log.timestamp = datadict['timestamp']\n            log.save()\n        elif datadict['hinweiscode'] in ['50']:\n            info.insert(0, 'Fehlte bei der Entladung')\n            log = huLOG.models.PackstueckLogentry(packstueck=packstueck)\n            log.displaytext = ', '.join(info)\n            log.sourcedata = repr(datadict)\n            log.source = 'Maeuler ENTL'\n            log.code = '500'\n            log.timestamp = datadict['timestamp']\n            log.save()\n        else:\n            logging.error('unknown ENTL data: %r (%r)' % (datadict['hinweiscode'],\n                                                          datadict['hinweistext']))\n\n    def parse(self, data):\n        \"\"\"Parses Fortras ENTL data.\"\"\"\n        lines = data.split('\\n')\n        if not lines[0].startswith('@@PHENTL128 0128003500107 MAEULER HUDORA1                       '):\n            raise RuntimeError(\"illegal status data %r\" % data[:300])\n        for line in lines[1:]:\n            line = line.strip('\\r')\n            if not line:\n                continue\n            if line[0] == 'M':\n                match = re.search(Entladebericht.m_record_re, line)\n                if not match:\n                    print 'no match', repr(line)\n                # the content of M records are ignored\n            elif line[0] == 'N':\n                match = re.search(Entladebericht.n_record_re, line)\n                if not match:\n                    print 'no match', repr(line)\n                # the content of N records are ignored\n            elif line[0] == 'V':\n                match = re.search(Entladebericht.v_record_re, line)\n                newdict = {}\n                if not match:\n                    print 'no match', repr(line)\n                for key, value in match.groupdict().items():\n                    newdict[key] = value.strip()\n                newdict['timestamp'] = datetime.datetime(int(newdict['date'][4:]), int(newdict['date'][2:4]),\n                                                         int(newdict['date'][:2]), int(newdict['time'][:2]),\n                                                         int(newdict['time'][2:]))\n                newdict['sendungsnrversender'] = newdict['sendungsnrversender']\n                self.update_packstueck(newdict['nve'], newdict)\n\n            elif line[0] == 'W':\n                pass  # we happyly ignore W records\n            else:\n                print \"unknown %r\" % line\n"
  },
  {
    "path": "pyshipping/fortras/fakt.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n\"\"\"\nfakt.py\n\nCreated by Christian Klein on 2011-03-31.\nCopyright (c) 2011 HUDORA GmbH. All rights reserved.\n\"\"\"\n\nimport csv\nimport datetime\nimport decimal\nimport StringIO\n\n\nFIELDNAME_MAPPING = {\n    'Firma': 'firma',\n    'T-Datum': 'datum',\n    'Frachtbrief': 'frachtbrief',\n    'Versender-Ref.': 'lieferung_id',\n    'Abs.-Name': 'absender_name1',\n    'Abs.-Str.': 'absender_strasse',\n    'Abs.-Land': 'absender_land',\n    'Abs.-Plz': 'absender_plz',\n    'Abs.-Ort': 'absender_ort',\n    'Emp.-Name': 'empfaenger_name1',\n    'Emp.-Str.': 'empfaenger_strasse',\n    'Emp.-Land': 'empfaenger_land',\n    'Emp.-Plz': 'empfaenger_plz',\n    'Emp.-Ort': 'empfaenger_ort',\n    'Zeichen+Nr.': 'zeichennr',\n    'Inhalt': 'inhalt',\n    'T-Gewicht': 'transportgewicht',\n    'F-Gewicht': 'frachtpflgewicht',\n    'KM': 'kilometer',\n    'VPE': 'einheiten',\n    'EURO': 'euro',\n    'GIBO': 'gibo',\n    'Fracht': 'fracht',\n    'Maut': 'maut',\n    'Summe': 'summe'}\n\n\ndef convert_to_decimal(value, factor=None):\n    \"\"\"Convert a pseudo decimal value to Decimal\"\"\"\n\n    value = decimal.Decimal(value.replace(',', '.'))\n    if factor:\n        value *= factor\n    return value\n\n\ndef convert_record(record):\n    \"\"\"Convert fields in record to Python data types\"\"\"\n\n    if 'datum' in record:\n        record['datum'] = datetime.datetime.strptime(record['datum'], '%d.%m.%Y')\n\n    if 'kilometer' in record:\n        record['kilometer'] = convert_to_decimal(record['kilometer'])\n\n    for key in 'fracht', 'frachteinheiten', 'maut', 'kosten':\n        if key in record:\n            record[key] = convert_to_decimal(record[key], factor=100)\n\n    for key in 'transportgewicht', 'frachtpflgewicht':\n        if key in record:\n            record[key] = convert_to_decimal(record[key], factor=1000)\n\n    if None in record:\n        del record[None]\n\n    return record\n\n\ndef parse_fakt(data):\n    \"\"\"Parse BORDERO FAKT data\"\"\"\n\n    if isinstance(data, basestring):\n        data = StringIO.StringIO(data)\n\n    header = data.readline()\n    if not header.strip() == '@@PHFAKT128 FROMAT:CSV DELIMITER:;':\n        raise ValueError('Unknown header: %r' % header)\n\n    reader = csv.DictReader(data, delimiter=';')\n    fieldnames = reader.fieldnames\n    for index, field in enumerate(fieldnames):\n        if field in FIELDNAME_MAPPING:\n            del fieldnames[index]\n            fieldnames.insert(index, FIELDNAME_MAPPING[field])\n    reader.fieldnames = fieldnames\n\n    # rows = dict((row['zeichennr'], convert_record(row)) for row in reader)\n    rows = [convert_record(row) for row in reader]\n    return rows\n\n\nif __name__ == \"__main__\":\n    import sys\n    import pprint\n    pprint.pprint(parse_fakt(open(sys.argv[1]).read()))\n"
  },
  {
    "path": "pyshipping/fortras/fortras_stat.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n\"\"\"\nstat.py - parse Fortras STAT messages.\n\nCreated by Maximillian Dornseif on 2006-11-19.\nYou may consider this BSD licensed.\n\"\"\"\n\nimport re\nimport datetime\nimport logging\n\n\nclass Statusmeldung(object):\n    \"\"\"Parses and represents an STAT message.\"\"\"\n    q_record_re = (r'Q(?P<idbeimempfangspartner>.{10})(?P<verkehrsart>.)(?P<sendungsnrversender>.{16})'\n                   + r'(?P<sendungsnrempfaenger>.{16})(?P<sendungsschluessel>[0-9 ]{3})(?P<date>[0-9 ]{8})'\n                   + r'(?P<time>[0-9 ]{4})(?P<wartezeit>[0-9 ]{3})(?P<quittungsgeber>.{15})(?P<zusatztext>.{49})'\n                   + r'(?P<foo>.)5')\n    # Satzart ‘Q’ muss 1 001 - 001\n    # Identifkations-Nr. des Versandpartners beim Empfangspartner** muss          10 002 - 011\n    # Verkehrsart muss 1 012 - 012\n    # Sendungs-Nr. Versandpartner muss          16 013 - 028\n    # Sendungs-Nr.Empfangspartner muss          16 029 - 044\n    # Statusschlüssel* muss 3 045 - 047\n    # Datum des Ereignisses\n    # (TTMMJJJJ) muss 8/0 048 - 055\n    # Uhrzeit des Ereignisses\n    # (SSMM) muss 4/0 056 - 059\n    # Warte-/Standzeit in Minuten kann 3 060 - 062\n    # Name des Quittungsgebers kann          15 063 - 077\n    # Zusatztext kann          49 078 - 126\n    # Frei 1 127 - 127\n    # Releasestand ‘5’ muss 1 128 - 128\n\n    # stati, die dazu fuehren, dass eine  sendung erledigt ist\n    erledigtstati = [11,  # Nicht in Zustellung - Sendung endgültig in Verlust\n                     12,  # Zugestellt - reine Quittung\n                     66,  # Keine Zustellung - Komplett-Fehlmenge per Entladebericht gemeldet\n                     67,  # Nicht zugestellt - annahmeverweigerte Sendung, Retour lt. Verfügung\n                     68,  # Nicht zugestellt - annahmeverweigerte Sendung, Retour-Verfügung fehlt\n                     69,  # Zugestellt - mit Teilfehlmenge\n                     70,  # Zugestellt - mit Beschädigung\n                     71,  # Zugestellt - Zustellung nicht belegbar/SPÜS verloren\n                     74,  # Zugestellt - Teil-AV\n                     80,  # AV - Ware beschädigt\n                     81,  # AV - Fehlmenge\n                     82,  # AV - Liefertermin überschritten\n                     83,  # AV - Lieferschein fehlt/Begleitpapiere unvollständig\n                     84,  # AV - Empfänger zahlt WWNN/EUSt. nicht\n                     85,  # AV - nicht bestellt\n                     88,  # Erledigung durch ...\n                   ]\n    okstati = [12, 50]\n    bouncestati = [80, 81, 82, 83, 84, 85]\n    errorstati = [66, 67, 68, 69, 70, 71, 74] + bouncestati\n    warnstati = [5, 8, 9, 18, 63, 64, 65, 66, 99, 100] + bouncestati\n    statustexte = {\n         1: 'Nicht in Zustellung - Sendung fehlt komplett lt. EB',\n         2: 'Nicht in Zustellung - unvollständige/falsche Sendungsangaben',\n         3: 'Nicht in Zustellung – Ware beschädigt',\n         4: 'Nicht in Zustellung - Sendung verstapelt/nicht auffindbar',\n         5: 'Nicht in Zustellung - Platzmangel auf Zustelltour',\n         6: 'Nicht in Zustellung - Fix- oder Zustelltermin lt. Vorgabe',\n         7: 'Nicht in Zustellung - Empfänger außerhalb des Produktionsgebietes (24 h)',\n         8: 'Nicht zugestellt - Zeitmangel auf Zustelltour',\n         9: 'Nicht zugestellt - Empfänger nicht angetroffen.  Zustellbescheid hinterlassen',\n        10: 'Nicht zugestellt - annahmeverweigert/unzustellbar  oder Adressfehler',\n        11: 'Nicht in Zustellung - Sendung endgültig in Verlust (Protokoll folgt)',\n        12: 'Zugestellt - reine Quittung',\n        13: 'Nicht in Zustellung - Sendung unvollständig',\n        14: 'Nicht in Zustellung - Empfänger ist Selbstabholer',\n        15: 'Nicht in Zustellung – Regionaler Feiertag',\n        16: 'Nicht in Zustellung - Zollgut',\n        17: 'Nicht in Zustellung - Avisvorschrift lt. Beleg/Anweisung/Inkasso',\n        18: 'Nicht zugestellt - zu lange Wartezeit beim Empfünger',\n        22: 'Nicht in Zustellung - Lieferschein fehlt/Begleitpapiere  unvollstündig',\n        31: 'Nicht in Zustellung - Fernverkehr vom VP/HUB  verspätet (E2-Status)',\n        40: 'Sendung auf dem Weg zum Empfänger / in Zustellung',\n        50: 'Zugestellt - reine Quittung',\n        52: 'Sendung zu spät beim Empfangsspediteur angekommen, Auslieferung verspätet sich',\n        53: 'Sendung für Zustellung vorgesehen / Nachladesdg',\n        54: 'Sendung auf dem Weg zum Empfänger / in Zustellung',\n        55: 'Nicht in Zustellung - keine Warenannahme oder Vereinbarung',\n        63: 'Nicht zugestellt - keine Warennahme oder Warenannahme geschlossen',\n        64: 'Nicht zugestellt - Hebebühne erforderlich',\n        65: 'Nicht zugestellt - Empfänger wünscht Avis oder Fixtermin',\n        66: 'Keine Zustellung - Komplett-Fehlmenge per Entladebericht gemeldet',\n        67: 'Nicht zugestellt - annahmeverweigerte Sendung, Retour lt. Verfügung',\n        68: 'Nicht zugestellt - annahmeverweigerte Sendung, Retour-Verfügung fehlt',\n        69: 'Zugestellt - mit Teilfehlmenge',\n        70: 'Zugestellt - mit Beschädigung',\n        71: 'Zugestellt - Zustellung nicht belegbar/SPÜS verloren',\n        74: 'Zugestellt - Teil-AV',\n        80: 'AV - Ware beschädigt',\n        81: 'AV - Fehlmenge',\n        82: 'AV - Liefertermin überschritten',\n        83: 'AV - Lieferschein fehlt/Begleitpapiere unvollständig',\n        84: 'AV - Empfänger zahlt WWNN/EUSt. nicht',\n        85: 'AV - nicht bestellt',\n        88: 'Erledigung durch ...',\n        90: 'Übermittlung des Quittungsgebers, des Zustelldatums und der Ablieferzeit',\n        91: 'Zustellbeleg archiviert',\n        99: 'Nicht in Zustellung - Ereignis zu Lasten Empfangspartner',\n        100: 'Nicht in Zustellung - Ereignis nicht zu Lasten Empfangspartner',\n    }\n\n    def update_sendung(self, sendung_id, datadict):\n        \"\"\"Updates a huLOG Sendung record with the parsed STAT data.\"\"\"\n        import huLOG.models\n        try:\n            sendung = huLOG.models.Sendung.objects.get(id=sendung_id)\n        except huLOG.models.Sendung.DoesNotExist:\n            logging.warning('Problem locating Sendung with id %r for STAT record - ignoring' % sendung_id)\n            return\n        if datadict['timestamp'] > datetime.datetime.now():\n            logging.warning('Future timestamp for Sendung with id %r: %s' % (sendung_id, datadict['timestamp']))\n            return\n        if datadict['timestamp'] < sendung.created_at:\n            logging.warning('Inconsistent timestamps for Sendung with id %r: %s' % (sendung_id, datadict['timestamp']))\n            return\n        if int(datadict['sendungsschluessel']) in Statusmeldung.warnstati:\n            sendung.needs_attention = True\n        if int(datadict['sendungsschluessel']) in Statusmeldung.erledigtstati:\n            sendung.delivered_at = datadict['timestamp']\n            sendung.status = 'delivered'\n        if int(datadict['sendungsschluessel']) in Statusmeldung.errorstati:\n            sendung.needs_attention = True\n            sendung.status = 'error'\n        if int(datadict['sendungsschluessel']) in Statusmeldung.bouncestati:\n            sendung.status = 'bounced'\n            logging.warning('bounced shipment %r: %r (%r|%r)' % (datadict['sendungsnrversender'],\n                                                                       datadict['sendungsschluessel'],\n                                                                       datadict['statustext'],\n                                                                       datadict['zusatztext']))\n\n        if datadict['statustext']:\n            info = [datadict['statustext']]\n        else:\n            info = ['']\n        if datadict['quittungsgeber']:\n            info.append('quittiert durch %r' % datadict['quittungsgeber'])\n        if datadict['wartezeit']:\n            info.append('Wartezeit % min' % int(datadict['wartezeit']))\n        if datadict['zusatztext']:\n            info.append(datadict['zusatztext'])\n        info.append(str(datadict['timestamp']))\n        log = huLOG.models.SendungLogentry(lieferung=sendung)\n        log.displaytext = repr(', '.join(info))\n        log.sourcedata = repr(datadict)\n        log.source = 'Maeuler STAT'\n        log.code = '200'\n        if datadict['sendungsschluessel'] in Statusmeldung.warnstati:\n            log.code = '220'\n        if datadict['sendungsschluessel'] in Statusmeldung.errorstati:\n            log.code = '230'\n        log.timestamp = datadict['timestamp']\n        log.save()\n        sendung.updated_at = datetime.datetime.now()\n\n        # we only save well known stati\n        if datadict['sendungsschluessel'] in ['005', '006', '007', '008', '009', '012', '015', '016', '017',\n                                              '018', '031', '040', '050', '054', '055', '063', '068', '069',\n                                              '071', '084', '085', '091', '099', '053', '100', '999']:\n            if datadict['sendungsnrempfaenger']:\n                if (sendung.speditionsauftragsnummer\n                  and sendung.speditionsauftragsnummer != datadict['sendungsnrempfaenger']):\n                    logging.error('Problem with Sendung %r: original speditionsauftragsnummer %r replaced by %r' % (sendung, sendung.speditionsauftragsnummer, datadict['sendungsnrempfaenger']))\n                sendung.speditionsauftragsnummer = datadict['sendungsnrempfaenger']\n        else:\n            logging.error('unknown STAT data for record %r: %r (%r|%r)' % (datadict['sendungsnrversender'],\n                                                                           datadict['sendungsschluessel'],\n                                                                           datadict['statustext'],\n                                                                           datadict['zusatztext']))\n        sendung.save()\n\n    def parse(self, data):\n        \"\"\"Parses Fortras STAT data.\"\"\"\n        lines = data.split('\\n')\n        lines = [x.strip('\\r') for x in lines]\n        if lines[0] == '' and len(lines) == 1:\n            logging.error('empty file')\n            return\n        if not lines[0].startswith('@@PHSTAT128 0128003500107 MAEULER HUDORA1                       '):\n            raise RuntimeError(\"illegal status data %r\" % data[:300])\n        for line in lines[1:]:\n            if not line or line[0] == 'X':  # 'X' records and empty lines are ignored\n                continue\n            match = re.search(Statusmeldung.q_record_re, line)\n            newdict = {}\n            if not match:\n                print 'no match', repr(line)\n            for key, value in match.groupdict().items():\n                newdict[key] = value.strip()\n            try:\n                if newdict['time']:\n                    newdict['timestamp'] = datetime.datetime(int(newdict['date'][4:]), int(newdict['date'][2:4]),\n                                                             int(newdict['date'][:2]), int(newdict['time'][:2]),\n                                                             int(newdict['time'][2:]))\n                else:\n                    newdict['timestamp'] = datetime.datetime(int(newdict['date'][4:]), int(newdict['date'][2:4]),\n                                                             int(newdict['date'][:2]))\n            except ValueError:\n                logging.error(\"malformed timestamp %r|%r\" % (newdict['date'], newdict['time']))\n                newdict['timestamp'] = datetime.datetime.now()\n            newdict['statustext'] = Statusmeldung.statustexte.get(int(newdict['sendungsschluessel']))\n            try:\n                newdict['sendungsnrversender'] = int(newdict['sendungsnrversender'])\n                self.update_sendung(newdict['sendungsnrversender'], newdict)\n            except ValueError:\n                logging.warning('Problem with invalid id %r for STAT record - ignoring' % newdict['sendungsnrversender'])\n"
  },
  {
    "path": "pyshipping/fortras/test.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n\"\"\"\ntest.py - tests for fortras related functionality\n\nCreated by Maximillian Dornseif on 2006-11-19.\nYou may consider this BSD licensed.\n\"\"\"\n\nimport unittest\nfrom pyshipping.fortras.bordero import _clip, Bordero\n\n_nvecount = 0\n\n\nclass TestPackstueck:\n\n    def __init__(self):\n        global _nvecount\n        self.gewicht = 160000\n        self.nve = '%018d' % (_nvecount)\n        _nvecount += 1\n        self.trackingnummer = self.nve\n\n\nclass TestLieferung:\n\n    def __init__(self):\n        global _nvecount\n        self.packstuecke = [TestPackstueck(), TestPackstueck()]\n        self.lieferscheinnummer = '123456'\n        self.auftragsnummer = '654321'\n        self.auftragsnummer_kunde = 'auftragsnummer_kunde'\n        self.kundennummer = '54321'\n        self.name1 = 'name1-Iñtërnâtiônàlizætiøn-latin1-name1-name1'\n        self.name2 = 'name2zulangname2zulangname2zulangname2zulangname2zulangname2zulangname2n'\n        self.name3 = 'name3'\n        self.name4 = 'nane4'\n        self.adresse = 'strassestrassestrassestrassestrassestrassestrasse'\n        self.plz = 'plz99999'\n        self.ort = 'ortortortortortortortortortortortortortortort'\n        self.land = 'DE'\n        self.spedition = 'Maeuler'\n        self.frankatur = 'frei'\n        self.codedata = 'AAAA'\n        self.hebebuehne = True\n        self.avisieren_unter = '02195-8393'\n        self.versandhinweis = 'versandhinweis'\n        self.info = 'info'\n        self.fixtermin = False\n        self.code = 'c0de'\n        self.id = _nvecount\n        _nvecount += 1\n\n    def _get_gewicht(self):\n        return sum([packstueck.gewicht for packstueck in self.packstuecke])\n    gewicht = property(_get_gewicht)\n\n\nclass BorderoTests(unittest.TestCase):\n\n    def test_clip(self):\n        self.assertEqual(_clip(3, 'ABCDEF'), 'ABC')\n        self.assertEqual(_clip(3, 'ABC'), 'ABC')\n        self.assertEqual(_clip(5, 'ABC'), 'ABC')\n        self.assertEqual(_clip(0, 'ABC'), '')\n\n    def test_bordero(self):\n        bordero = Bordero()\n        bordero.borderonr = 1\n        bordero.add_lieferung(TestLieferung())\n        bordero.add_lieferung(TestLieferung())\n        bordero.add_lieferung(TestLieferung())\n        bordero.generate_dataexport()\n\n\n# The following tests have an ugly circular dependency to huLOG - this needs fixing\n\n# class StatTests(unittest.TestCase):\n#\n#     def test_status(self):\n#         tesdata = '''@@PHSTAT128 0128003500107 MAEULER HUDORA1\n# Q11515     L00000000000000545177124         091071120061106                                                                    5\\\n# '''\n#         stat = Statusmeldung()\n#         #stat.parse(tesdata)\n#\n#         testdata = '''@@PHSTAT128 0128003500107 MAEULER HUDORA1\n# Q11515     L00000000000000395176882         054061120061615                  ROLLKARTE 421/6902                                5\n# Q11515     L00000000000000385176883         054061120061615                  ROLLKARTE 421/6902                                5\n# Q11515     L00000000000000375176884         054061120061615                  ROLLKARTE 421/6902                                5\n# Q11515     L00000000000000365176885         054061120061615                  ROLLKARTE 421/6902                                5\n# Q11515     L00000000000000355176886         054061120061615                  ROLLKARTE 421/6902                                5\n# Q11515     L00000000000000345176887         054061120061615                  ROLLKARTE 421/6902                                5\n# Q11515     L00000000000000335176888         054061120061615                  ROLLKARTE 421/6902                                5\n# Q11515     L00000000000000325176890         054061120061615                  ROLLKARTE 421/6902                                5\n# '''\n#         stat.parse(testdata)\n#\n#         testdata = '''@@PHSTAT128 0128003500107 MAEULER HUDORA1\n# Q11515     L00000000000000395176882         012111120061111   UNTERSCHRIFT                                                     5\n# Q11515     L00000000000000385176883         012111120061111   UNTERSCHRIFT                                                     5\n# Q11515     L00000000000000375176884         012111120061111   UNTERSCHRIFT                                                     5\n# Q11515     L00000000000000365176885         012111120061111   UNTERSCHRIFT                                                     5\n# Q11515     L00000000000000355176886         012111120061111   UNTERSCHRIFT                                                     5\n# Q11515     L00000000000000345176887         012111120061111   UNTERSCHRIFT                                                     5\n# Q11515     L00000000000000335176888         012111120061111   UNTERSCHRIFT                                                     5\n# Q11515     L00000000000000325176890         012111120061111   UNTERSCHRIFT                                                     5\n# '''\n#         stat.parse(testdata)\n#\n#         testdata = '''@@PHSTAT128 0128003500107 MAEULER HUDORA1\n# Q11515     L00000000009106405176617         012031120061500   TESTNAME                                                         5\n# Q11515     L00000000009109205176618         012031120061501   TESTNAME2                                                        5\n# Q11515     L00000000009111205176619         012031120061510   TESTNAME3                                                        5\n# '''\n#         stat.parse(testdata)\n#\n#         testdata = '''@@PHSTAT128 0128003500107 MAEULER HUDORA1\n# Q11515     L65827812        5178928         054171120060745                                                                    5\n# Q11515     L65827812        5178928         012171120060827   THOMA                                                            5\n# Q11515     L232740          5178930         054171120060922                                                                    5\n# '''\n#         stat.parse(testdata)\n#\n#         testdata = '''@@PHSTAT128 0128003500107 MAEULER HUDORA1\n# Q11515     L6350706         5178934         054171120060726                                                                    5\n# Q11515     L6350706         5178934         012171120060911   PATER                                                            5\n# '''\n#         stat.parse(testdata)\n#\n#\n# class EntlTests(unittest.TestCase):\n#\n#     def test_entladebericht(self):\n#         entl = Entladebericht()\n#\n#         testdata = '''@@PHENTL128 0128003500107 MAEULER HUDORA1\n# M0000000000000000000311200611515     301218990000  301218990000301218990000                                                    5\n# W                                                                                                                    EM        5\n# N00000000000000000000100000000009106405176617         000                                    0000\n# N00000000000000000000200000000009109205176618         000                                    0000\n# N00000000000000000000300000000009111205176619         000                                    0000\n# '''\n#         entl.parse(testdata)\n#\n#         tesdata = '''@@PHENTL128 0128003500107 MAEULER HUDORA1\n# M0000000000000000081611200611515     161120061300  161120061300161120061310                                                    5\n# W                                                                                                                    EM        5\n# V0000000000000000080000000000000074   00340059980000001166               0                          16112006184200\n# '''\n#         entl.parse(testdata)\n#\n#         testdata = '''@@PHENTL128 0128003500107 MAEULER HUDORA1\n# M0000000000000000091711200611515     171120061300  171120061310171120061320                                                    5\n# W                                                                                                                    EM        5\n# V0000000000000000090000000000000104   00340059980000001470               0                          17112006133900\n# V0000000000000000090000000000000103   00340059980000001463               0                          17112006134000\n# V0000000000000000090000000000000102   00340059980000001456               0                          17112006133900\n# V0000000000000000090000000000000101   00340059980000001449               0                          17112006133900\n# V0000000000000000090000000000000100   00340059980000001432               0                          17112006134000\n# V0000000000000000090000000000000099   00340059980000001425               0                          17112006133900\n# V0000000000000000090000000000000098   00340059980000001418               0                          17112006133900\n# V0000000000000000090000000000000097   00340059980000001401               0                          17112006133900\n# V0000000000000000090000000000000096   00340059980000001395               0                          17112006133900\n# V0000000000000000090000000000000095   00340059980000001388               0                          17112006133900\n# V0000000000000000090000000000000094   00340059980000001371               0                          17112006134000\n# V0000000000000000090000000000000093   00340059980000001364               0                          17112006134000\n# V0000000000000000090000000000000092   00340059980000001357               0                          17112006134000\n# V0000000000000000090000000000000091   00340059980000001340               0                          17112006134000\n# V0000000000000000090000000000000090   00340059980000001333               0                          17112006134000\n# V0000000000000000090000000000000089   00340059980000001326               0                          17112006133900\n# V0000000000000000090000000000000088   00340059980000001319               0                          17112006133900\n# '''\n#         entl.parse(testdata)\n#\n#         testdata = '''@@PHENTL128 0128003500107 MAEULER HUDORA1\n# M0000000000000000081611200611515     161120061300  161120061300161120061310                                                    5\n# W                                                                                                                    EM        5\n# V0000000000000000080000000000000084   00340059980000001272               0                          16112006184200\n# V0000000000000000080000000000000083   00340059980000001265               0                          16112006142912\n# V0000000000000000080000000000000082   00340059980000001258               0                          16112006184200\n# V0000000000000000080000000000000081   00340059980000001241               0                          16112006142913\n# V0000000000000000080000000000000080   00340059980000001234               0                          16112006142913\n# V0000000000000000080000000000000079   00340059980000001227               0                          16112006142913\n# V0000000000000000080000000000000078   00340059980000001210               0                          16112006142913\n# V0000000000000000080000000000000077   00340059980000001203               0                          16112006142914\n# V0000000000000000080000000000000076   00340059980000001197               0                          16112006184200\n# V0000000000000000080000000000000075   00340059980000001180               0                          16112006142914\n# V0000000000000000080000000000000074   00340059980000001173               0                          16112006184200\n# V461               720-00             00340498430009431112               0                          19092008183100\n# '''\n#         entl.parse(testdata)\n#\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "pyshipping/package.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n\"\"\"\npackage.py - shipping/cargo related calculations based on a unit of shipping (box, crate, package)\n\nCreated by Maximillian Dornseif on 2006-12-02.\nCopyright HUDORA GmbH 2006, 2007, 2010\nYou might consider this BSD-Licensed.\n\"\"\"\n\nimport doctest\nimport unittest\n\n\nclass Package(object):\n    \"\"\"Represents a package as used in cargo/shipping aplications.\"\"\"\n\n    def __init__(self, size, weight=0, nosort=False):\n        \"\"\"Generates a new Package object.\n\n        The size can be given as an list of integers or an string where the sizes are\n        separated by the letter 'x':\n        >>> Package((300, 400, 500))\n        <Package 500x400x300>\n        >>> Package('300x400x500')\n        <Package 500x400x300>\n        \"\"\"\n        self.weight = weight\n        if \"x\" in size:\n            self.heigth, self.width, self.length = [int(x) for x in size.split('x')]\n        else:\n            self.heigth, self.width, self.length = size\n        if not nosort:\n            (self.heigth, self.width, self.length) = sorted((int(self.heigth), int(self.width),\n                                                             int(self.length)), reverse=True)\n        self.volume = self.heigth * self.width * self.length\n        self.size = (self.heigth, self.width, self.length)\n\n    def _get_gurtmass(self):\n        \"\"\"'gurtamss' is the circumference of the box plus the length - which is often used to\n            calculate shipping costs.\n\n            >>> Package((100,110,120)).gurtmass\n            540\n        \"\"\"\n\n        dimensions = (self.heigth, self.width, self.length)\n        maxdimension = max(dimensions)\n        otherdimensions = list(dimensions)\n        del otherdimensions[otherdimensions.index(maxdimension)]\n        return maxdimension + 2 * (sum(otherdimensions))\n    gurtmass = property(_get_gurtmass)\n\n    def hat_gleiche_seiten(self, other):\n        \"\"\"Prüft, ob other mindestens eine gleich grosse Seite mit self hat.\"\"\"\n\n        meineseiten = set([(self.heigth, self.width), (self.heigth, self.length), (self.width, self.length)])\n        otherseiten = set([(other.heigth, other.width), (other.heigth, other.length),\n                           (other.width, other.length)])\n        return bool(meineseiten.intersection(otherseiten))\n\n    def __getitem__(self, key):\n        \"\"\"The coordinates can be accessed as if the object is a tuple.\n        >>> p = Package((500, 400, 300))\n        >>> p[0]\n        500\n        \"\"\"\n        if key == 0:\n            return self.heigth\n        if key == 1:\n            return self.width\n        if key == 2:\n            return self.length\n        if isinstance(key, tuple):\n            return (self.heigth, self.width, self.length)[key[0]:key[1]]\n        if isinstance(key, slice):\n            return (self.heigth, self.width, self.length)[key]\n        raise IndexError\n\n    def __contains__(self, other):\n        \"\"\"Checks if on package fits within an other.\n\n        >>> Package((1600, 250, 480)) in Package((1600, 250, 480))\n        True\n        >>> Package((1600, 252, 480)) in Package((1600, 250, 480))\n        False\n        \"\"\"\n        return self[0] >= other[0] and self[1] >= other[1] and self[2] >= other[2]\n\n    def __hash__(self):\n        return self.heigth + (self.width << 16) + (self.length << 32)\n\n    def __eq__(self, other):\n        \"\"\"Package objects are equal if they have exactly the same dimensions.\n\n           Permutations of the dimensions are considered equal:\n\n           >>> Package((100,110,120)) == Package((100,110,120))\n           True\n           >>> Package((120,110,100)) == Package((100,110,120))\n           True\n        \"\"\"\n        return (self.heigth == other.heigth and self.width == other.width and self.length == other.length)\n\n    def __cmp__(self, other):\n        \"\"\"Enables to sort by Volume.\"\"\"\n        return cmp(self.volume, other.volume)\n\n    def __mul__(self, multiplicand):\n        \"\"\"Package can be multiplied with an integer. This results in the Package beeing\n           stacked along the biggest side.\n\n           >>> Package((400,300,600)) * 2\n           <Package 600x600x400>\n           \"\"\"\n        if self.weight:\n            new_weight = self.weight * multiplicand\n        else:\n            new_weight = None\n        return Package((self.heigth, self.width, self.length * multiplicand), new_weight)\n\n    def __add__(self, other):\n        \"\"\"\n            >>> Package((1600, 250, 480)) + Package((1600, 470, 480))\n            <Package 1600x720x480>\n            >>> Package((1600, 250, 480)) + Package((1600, 480, 480))\n            <Package 1600x730x480>\n            >>> Package((1600, 250, 480)) + Package((1600, 490, 480))\n            <Package 1600x740x480>\n            \"\"\"\n        meineseiten = set([(self.heigth, self.width), (self.heigth, self.length),\n                           (self.width, self.length)])\n        otherseiten = set([(other.heigth, other.width), (other.heigth, other.length),\n                           (other.width, other.length)])\n        if not meineseiten.intersection(otherseiten):\n            raise ValueError(\"%s has no fitting sites to %s\" % (self, other))\n        candidates = sorted(meineseiten.intersection(otherseiten), reverse=True)\n        stack_on = candidates[0]\n        mysides = [self.heigth, self.width, self.length]\n        mysides.remove(stack_on[0])\n        mysides.remove(stack_on[1])\n        othersides = [other.heigth, other.width, other.length]\n        othersides.remove(stack_on[0])\n        othersides.remove(stack_on[1])\n\n        if self.weight and other.weight:\n            new_weight = self.weight + other.weight\n        else:\n            new_weight = None\n\n        return Package((stack_on[0], stack_on[1], mysides[0] + othersides[0]), new_weight)\n\n    def __str__(self):\n        if self.weight:\n            return \"%dx%dx%d %dg\" % (self.heigth, self.width, self.length, self.weight)\n        else:\n            return \"%dx%dx%d\" % (self.heigth, self.width, self.length)\n\n    def __repr__(self):\n        if self.weight:\n            return \"<Package %dx%dx%d %d>\" % (self.heigth, self.width, self.length, self.weight)\n        else:\n            return \"<Package %dx%dx%d>\" % (self.heigth, self.width, self.length)\n\n\ndef buendelung(kartons, maxweight=31000, maxgurtmass=3000):\n    \"\"\"Versucht Pakete so zu bündeln, so dass das Gurtmass nicht überschritten wird.\n\n    Gibt die gebündelten Pakete und die nicht bündelbaren Pakete zurück.\n\n    >>> buendelung([Package((800, 310, 250)), Package((800, 310, 250)), Package((800, 310, 250)), Package((800, 310, 250))])\n    (1, [<Package 800x750x310>], [<Package 800x310x250>])\n    >>> buendelung([Package((800, 310, 250)), Package((800, 310, 250)), Package((800, 310, 250)), Package((800, 310, 250)), Package((450, 290, 250)), Package((450, 290, 250))])\n    (2, [<Package 800x750x310>, <Package 500x450x290>], [<Package 800x310x250>])\n    \"\"\"\n    kartons = list(kartons)\n\n    def buendelung_moeglich(box_a, box_b):\n        \"\"\"Entscheide, ob eine Bündelung der beiden Kartons möglich ist.\n\n        Es kann gebündelt werden, wenn die Summe der Gewichte (falls gepflegt)\n        kleiner ist als das maximale Gewicht,\n        das Gurtmaß nicht das maximale Gurtmaß übersteigt\n        und nicht bereits die maximale Anzahl der Bündelungen erreicht ist.\n        \"\"\"\n\n        if kartons_im_buendel > MAXKARTONSIMBUENDEL:\n            return False\n\n        tmp = box_a + box_b\n        if tmp.weight > maxweight:\n            return False\n        elif tmp.gurtmass > maxgurtmass:\n            return False\n        return True\n\n    MAXKARTONSIMBUENDEL = 6\n    if not kartons:\n        return 0, [], kartons\n    gebuendelt = []\n    rest = []\n    lastcarton = kartons.pop(0)\n    buendel = False\n    buendelcounter = 0\n    kartons_im_buendel = 1\n    while kartons:\n        currentcarton = kartons.pop(0)\n        # check if 2 dimensions fit and bundling is possible\n        if currentcarton.hat_gleiche_seiten(lastcarton) and buendelung_moeglich(lastcarton, currentcarton):\n            # new carton has the same size in two dimensions and the sum of both in the third\n            lastcarton = lastcarton + currentcarton\n            kartons_im_buendel += 1\n            if buendel is False:\n                # neues Bündel\n                buendelcounter += 1\n            buendel = True\n        else:\n            # different sizes, or too big\n            if buendel:\n                gebuendelt.append(lastcarton)\n            else:\n                rest.append(lastcarton)\n            kartons_im_buendel = 1\n            lastcarton = currentcarton\n            buendel = False\n    if buendel:\n        gebuendelt.append(lastcarton)\n    else:\n        rest.append(lastcarton)\n    return buendelcounter, gebuendelt, rest\n\n\ndef pack_in_bins(kartons, versandkarton):\n    \"\"\"Implements Bin-Packing.\n\n    You provide it with a bin size and a list of Package Objects to be bined. Returns a list of lists\n    representing the bins with the binned Packages and a list of Packages too big for binning.\n\n    >>> pack_in_bins([Package('135x200x250'), Package('170x380x390'), Package('485x280x590'), Package('254x171x368'), Package('201x172x349'), Package('254x171x368')], \\\n                     Package('600x400x400'))\n    ([[<Package 250x200x135>, <Package 349x201x172>, <Package 368x254x171>], [<Package 368x254x171>, <Package 390x380x170>]], [<Package 590x485x280>])\n    \"\"\"\n\n    import pyshipping.binpack\n    toobig, packagelist, bins, rest = [], [], [], []\n    for box in sorted(kartons, reverse=True):\n        if box not in versandkarton:\n            # passt eh nicht\n            toobig.append(box)\n        else:\n            packagelist.append(box)\n    if packagelist:\n        bins, rest = pyshipping.binpack.binpack(packagelist, versandkarton)\n    return bins, toobig + rest\n\n\n### Tests\nclass PackageTests(unittest.TestCase):\n    \"\"\"Simple tests for Package objects.\"\"\"\n\n    def test_init(self):\n        \"\"\"Tests object initialisation with different constructors.\"\"\"\n        self.assertEqual(Package((100, 100, 200)), Package(('100', '200', '100')))\n        self.assertEqual(Package((100.0, 200.0, 200.0)), Package('200x200x100'))\n\n    def test_eq(self):\n        \"\"\"Tests __eq__() implementation.\"\"\"\n        self.assertEqual(Package((200, 100, 200)), Package(('200', '100', '200')))\n        self.assertNotEqual(Package((200, 200, 100)), Package(('100', '100', '200')))\n\n    def test_volume(self):\n        \"\"\"Tests volume calculation\"\"\"\n        self.assertEqual(4000000, Package((100, 200, 200)).volume)\n        self.assertEqual(8000, Package((20, 20, 20)).volume)\n\n    def test_str(self):\n        \"\"\"Test __unicode__ implementation.\"\"\"\n        self.assertEqual('200x200x100', Package((100, 200, 200)).__str__())\n        self.assertEqual('200x200x100', Package('100x200x200').__str__())\n\n    def test_repr(self):\n        \"\"\"Test __repr__ implementation.\"\"\"\n        self.assertEqual('<Package 200x200x100 44>', Package((100, 200, 200), 44).__repr__())\n\n    def test_gurtmass(self):\n        \"\"\"Test gurtmass calculation.\"\"\"\n        self.assertEqual(800, Package((100, 200, 200)).gurtmass)\n        self.assertEqual(900, Package((100, 200, 300)).gurtmass)\n        self.assertEqual(1000, Package((200, 200, 200)).gurtmass)\n        self.assertEqual(3060, Package((1600, 250, 480)).gurtmass)\n\n    def test_mul(self):\n        \"\"\"Test multiplication.\"\"\"\n        self.assertEqual(Package((200, 200, 200)), Package((100, 200, 200)) * 2)\n\n    def test_sort(self):\n        \"\"\"Test multiplication.\"\"\"\n        data = [Package((1600, 490, 480)), Package((1600, 470, 480)), Package((1600, 480, 480))]\n        data.sort()\n        self.assertEqual(data,\n                         [Package((1600, 470, 480)), Package((1600, 480, 480)),\n                          Package((1600, 490, 480))])\n\n\nif __name__ == '__main__':\n\n    factor = 0\n    while True:\n        factor += 1\n        single = Package((750, 240, 220), 7400)\n        multi = single * factor\n        if multi.weight > 31000 or multi.gurtmass > 3000:\n            multi = single * (factor - 1)\n            #print factor - 1, multi, multi.gurtmass\n            break\n\n    doctest.testmod()\n    unittest.main()\n"
  },
  {
    "path": "pyshipping/shipment.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n\"\"\"\nsendung.py - Sendungsaufteilung - dies definiert eine abstrakte Sendung, mit Packstücken usw. und\n             Operationen darauf.\n\nCreated by Maximillian Dornseif on 2007-07-11. Based on code from 2007-04-17.\nCopyright (c) 2007 HUDORA GmbH. All rights reserved.\n\"\"\"\n\nimport unittest\nimport math\n\n\nclass AbstractPackstueck(object):\n    \"\"\"Definiert ein Packstück, d.h. eine Versandeinheit. In der Regel eine Palette oder ein Karton\"\"\"\n    pass\n\n\nclass AbstractItem(object):\n    \"\"\"Definiert ein Sendungsposition. In der Regel definiert als Artikel und Menge.\"\"\"\n    # Kann in der Theorie aus mehreren Packstücken bestehen, das ist aber noch nicht implementeirt.\n    def __init__(self):\n        # Wir gehen davon aus, dass folgende Attribute von ausserhalb oder von abgeleiteten Klassen\n        # definiert wird:\n        #self.gewicht_pro_exportkarton = None\n        #self.palettenfaktor = None\n        #self.produkte_pro_exportkarton = None\n        #self.einzelvolumen = None\n        #self.einzelgewicht = None\n        self.menge = None\n\n    def __unicode__(self):\n        if hasattr(self, 'liefertermin') and hasattr(self, 'artnr'):\n            return u\"%d x %s, %s\" % (self.menge, self.artnr, self.liefertermin)\n        if hasattr(self, 'artnr'):\n            return u\"%d x %s\" % (self.menge, self.artnr)\n        return u\"%d x ?????\" % (self.menge)\n\n    @property\n    def anbruch(self):\n        \"\"\"Returns True if this Item does not result in an export_package to be opened.\"\"\"\n        return (self.menge % self.produkte_pro_exportkarton) != 0\n\n    @property\n    def volumen(self):\n        \"\"\"Retuns the volume of this item in m^3.\"\"\"\n        # TODO: Exportkartonvolumen\n        return self.menge * self.einzelvolumen\n\n    @property\n    def gewicht(self):\n        \"\"\"Retuns the weight of this item in g.\"\"\"\n        # TODO: Exportkartongewicht usw.\n        return self.menge * self.einzelgewicht\n\n    @property\n    def max_packstueck_gewicht(self):\n        \"\"\"Returns the weigtht of the most heavy box for ths item.\"\"\"\n        if self.menge >= self.produkte_pro_exportkarton:\n            return self.gewicht_pro_exportkarton\n        return (self.gewicht_pro_exportkarton / self.produkte_pro_exportkarton) * self.menge\n\n    @property\n    def paletten(self):\n        \"\"\"Returns the number of pallets of this Item.\"\"\"\n        return float(self.menge) / float(self.palettenfaktor)\n\n    @property\n    def picks(self):\n        \"\"\"Returns the number storage locations to be accessed to get the item.\"\"\"\n        picks = self.paletten\n        # round up\n        if picks != int(picks):\n            picks = int(picks + 1)\n        return picks\n\n    @property\n    def export_kartons(self):\n        \"\"\"Returns the number of export packages needed to fullfill this item as a float.\"\"\"\n        return self.menge / float(self.produkte_pro_exportkarton)\n\n    @property\n    def export_karton_gewichte(self):\n        \"\"\"Returns the weights of the estimated number of packages which will be shipped in gramms.\"\"\"\n        menge = self.menge\n        ret = []\n        while menge:\n            if menge > self.produkte_pro_exportkarton:\n                ret.append(self.gewicht_pro_exportkarton)\n                menge -= self.produkte_pro_exportkarton\n            else:\n                ret.append(menge * self.einzelgewicht)\n                menge = 0\n        return ret\n\n    @property\n    def packstuecke(self):\n        \"\"\"Returns the absolute number of packages to fullfill this item as an integer.\n\n        This could take into account that packages can be bundled etc.\n        \"\"\"\n\n        packstuecke = self.export_kartons\n        # round up\n        if packstuecke != int(packstuecke):\n            packstuecke = int(packstuecke + 1)\n        return int(packstuecke)\n\n\nclass AbstractLieferung(object):\n    \"\"\"Definiert eine Lieferung. Das ist eine Einheit aus Positionen und Packstuecken.\"\"\"\n\n    def __init__(self):\n        # Wir gehen davon aus, dass folgende arrtibute von ausserhalb oder von abgeleiteten Klassen\n        # definiert wird:\n        self.fix = False  # the liefertermin is advisory or mandantory\n        self.liefertermin = None\n        self.itemlist = []\n\n    @property\n    def transportweg(self):\n        \"\"\"Returns the suggested method of shipping.\"\"\"\n        pass\n\n    @property\n    def transportzeit(self):\n        \"\"\"Returns the number of days this is likely to take to be shipped to the client.\"\"\"\n        pass\n\n    @property\n    def versandtermin(self):\n        \"\"\"Retuns the suggested date of shipping.\"\"\"\n        try:\n            return self.liefertermin - self.transportzeit\n        except TypeError:\n            return self.liefertermin\n\n    @property\n    def anbruch(self):\n        \"\"\"Returns False if this Lieferung contains no items which need a export_package to be opened.\"\"\"\n        for item in self.itemlist:\n            if item.anbruch:\n                return True\n        return False\n\n    @property\n    def volumen(self):\n        \"\"\"Returns the volume of all Items in this Lieferung in m^3.\"\"\"\n        return sum([x.volumen for x in self.itemlist])\n\n    @property\n    def gewicht(self):\n        \"\"\"Returns the gewicht of all Items in this Lieferung in g.\"\"\"\n        return sum([x.gewicht for x in self.itemlist])\n\n    @property\n    def max_packstueck_gewicht(self):\n        \"\"\"Returns the highest gewicht of any package in the shippment in g.\"\"\"\n        if self.itemlist:\n            return max([x.max_packstueck_gewicht for x in self.itemlist])\n        return 0\n\n    @property\n    def paletten(self):\n        \"\"\"Returns the number of pallets of all Items in this Lieferung.\"\"\"\n        return sum([x.paletten for x in self.itemlist])\n\n    @property\n    def versandpaletten(self):\n        \"\"\"Returns the number of pallets to be shipped. Always an Integer\"\"\"\n        return math.ceil(self.paletten)\n\n    @property\n    def picks(self):\n        \"\"\"Returns the number of estimated picks for this Lieferung.\n\n        A pick is defined as accessing a position in the warehouse.\"\"\"\n        return sum([x.picks for x in self.itemlist])\n\n    @property\n    def packstuecke(self):\n        \"\"\"Returns the number of \"Greifeinheiten\", meaning units to be taken out o the warehouse.\n        This is an integer.\"\"\"\n        return sum([x.packstuecke for x in self.itemlist])\n\n    @property\n    def export_kartons(self):\n        \"\"\"Returns the estimated number of packages which will be shipped. This is a float.\"\"\"\n        return sum([x.export_kartons for x in self.itemlist])\n\n    @property\n    def export_karton_gewichte(self):\n        \"\"\"Returns the weights of the estimated number of packages which will be shipped in gramms.\"\"\"\n        ret = []\n        for box in self.itemlist:\n            ret.extend(box.export_karton_gewichte)\n        return ret\n\n    @property\n    def kep(self):\n        \"\"\"Entscheidet, ob die Sendung mit einem Paketdienstleister verchickt werden kann.\"\"\"\n        if self.export_kartons > 10:\n            return False\n        if self.max_packstueck_gewicht > 31500:\n            return False\n        return True\n\n\nclass simpleTests(unittest.TestCase):\n    \"\"\"Very basic testing functionality.\"\"\"\n\n    def test_stupid(self):\n        \"\"\"Basic plausibility tests.\"\"\"\n        aitem = AbstractItem()\n        aitem.menge = 12\n        aitem.einzelgewicht = 3333\n        aitem.palettenfaktor = 70\n        aitem.produkte_pro_exportkarton = 2\n        aitem.einzelvolumen = (750 * 200 * 100) / 10 / 10 / 10\n        self.assertEqual(aitem.volumen, 180000)  # TODO: Unit? TOO BIG\n        self.assertEqual(aitem.gewicht, 39996)\n        self.assertEqual(aitem.anbruch, False)\n        self.assertAlmostEqual(aitem.paletten, 0.171428571429)\n        # print aitem.feinkommissionierung\n        aitem2 = AbstractItem()\n        aitem2.menge = 17\n        aitem2.einzelgewicht = 9123\n        aitem2.palettenfaktor = 30\n        aitem2.produkte_pro_exportkarton = 5\n        aitem2.einzelvolumen = (750 * 200 * 100) / 10 / 10 / 10\n\n        alieferung = AbstractLieferung()\n        alieferung.itemlist = [aitem]\n        self.assertEqual(alieferung.volumen, 180000)  # TODO: Unit? TOO BIG\n        self.assertEqual(alieferung.gewicht, 39996)\n        self.assertEqual(alieferung.anbruch, False)\n        self.assertAlmostEqual(alieferung.paletten, 0.171428571429)\n        # print alieferung.fix\n        # print alieferung.feinkommissionierung\n        # print alieferung.transportzeit\n        # print alieferung.versandtermin\n        # print alieferung.liefertermin\n        alieferung.itemlist = [aitem, aitem2]\n        self.assertEqual(alieferung.volumen, 435000)\n        self.assertEqual(alieferung.gewicht, 195087)\n        self.assertAlmostEqual(alieferung.paletten, 0.738095238095)\n        # print alieferung.transportweg\n        # print alieferung.fix\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "requirements.txt",
    "content": "# nothing\n"
  },
  {
    "path": "setup.py",
    "content": "long_description = \"\"\"\npyShipping provides connections to interface with shipping companies and to transport shipping related\ninformation.\n\"\"\"\n\nfrom setuptools import setup, find_packages\nfrom distutils.extension import Extension\nimport codecs\n\nsetup(name='pyShipping',\n      maintainer='Maximillian Dornseif',\n      maintainer_email='md@hudora.de',\n      url=\"https://github.com/hudora/pyShipping/\",\n      version='1.9',\n      description='pyShipping - Shipping related Toolkit',\n      long_description=codecs.open('README.rst', \"r\", \"utf-8\").read(),\n      classifiers=['License :: OSI Approved :: BSD License',\n                   'Intended Audience :: Developers',\n                   'Programming Language :: Python'],\n      # download_url\n      zip_safe=False,\n      packages=find_packages(),\n      package_data={'': ['README.rst'], 'pyshipping': ['carriers/dpd/georoutetables/*']},\n      include_package_data=True,\n      # cmdclass = {'build_ext': build_ext}\n)\n\n"
  },
  {
    "path": "testdata.txt",
    "content": "153x64x64 153x64x64\n580x140x60 580x140x60 \n580x190x60 580x190x60 \n580x210x60 580x210x60 \n240x62x43 240x62x43\n220x200x80 177x55x55\n133x133x50 210x210x80\n470x380x80 470x380x80\n133x133x50 133x133x50\n490x330x90 490x330x90\n440x180x70 440x180x70\n310x195x185 145x75x60\n133x133x50 133x133x50\n390x260x85 390x260x85\n500x300x80 500x300x80\n470x380x80 470x380x80\n500x300x80 500x300x80\n320x140x70 320x140x70\n490x330x90 490x330x90\n170x95x90 267x230x220\n490x330x90 490x330x90\n530x385x250 470x380x80\n310x230x70 340x280x120\n340x180x180 220x105x75\n470x380x80 220x160x120\n490x330x90 400x370x330\n290x210x160 585x285x200\n450x290x250 450x290x250\n298x241x216 298x241x216\n430x135x135 430x135x135\n370x350x340 535x400x180\n540x400x245 435x230x230\n260x205x135 260x205x135\n470x390x120 470x390x120\n390x285x140 190x190x120\n553x261x152 430x135x135\n260x205x135 580x260x255\n390x380x170 365x305x280\n185x140x138 185x140x138\n250x200x135 340x280x190\n260x155x105 340x280x190\n430x135x135 430x135x135\n430x300x200 430x300x200\n480x215x140 480x215x140\n185x140x138 185x140x138\n450x290x250 450x290x250\n520x330x320 520x330x320\n430x350x110 430x350x110\n580x235x200 580x235x200\n360x340x170 585x285x200\n270x240x160 270x240x160\n485x400x255 485x400x255\n375x365x215 390x365x273\n180x160x100 180x160x100\n400x390x170 580x235x200\n390x285x140 430x135x135\n360x340x170 430x135x135\n390x285x140 520x330x200\n450x290x250 430x135x135\n405x280x195 405x280x195\n430x135x135 430x135x135\n190x190x120 280x230x180\n390x285x140 280x230x180\n440x360x330 440x360x330\n373x323x283 473x323x283\n250x200x135 250x200x135\n390x330x210 480x215x140\n480x215x140 480x215x140\n185x165x100 185x165x100\n250x200x135 185x165x100\n390x285x140 390x285x140\n340x280x190 340x280x190\n345x220x130 340x280x190\n365x305x280 280x230x180\n185x165x100 185x165x100\n390x285x140 520x330x200\n520x330x320 510x300x150\n240x225x160 240x225x160\n230x53x53 230x53x53 230x53x53\n145x75x60 145x75x60 145x75x60\n145x75x60 140x110x25 140x110x25\n185x140x138 160x48x35 160x48x35\n410x250x50 410x250x50 410x250x50\n425x230x52 425x230x52 425x230x52\n490x330x90 490x330x90 490x330x90\n440x180x70 440x180x70 440x180x70\n490x330x90 490x330x90 260x205x135\n340x280x190 440x180x70 440x180x70\n490x330x90 490x330x90 190x190x120\n130x90x80 440x190x190 340x280x190\n360x340x170 320x140x70 320x140x70\n390x365x273 285x265x245 145x75x60\n390x365x273 295x190x170 145x75x60\n475x355x285 475x355x285 475x355x285\n215x205x105 215x205x105 215x205x105\n470x180x103 470x180x103 470x180x103\n195x150x114 195x150x114 245x165x120\n473x323x283 290x210x160 520x330x200\n553x261x152 290x210x160 430x135x135\n220x160x120 220x160x120 220x160x120\n220x210x180 220x210x180 220x210x180\n430x300x200 520x330x200 430x135x135\n298x241x216 298x241x216 298x241x216\n220x210x180 220x210x180 295x190x170\n470x180x103 470x180x103 470x180x103\n430x135x135 430x135x135 430x135x135\n520x330x200 365x305x280 580x260x255\n360x340x170 450x290x250 520x330x200\n520x330x200 430x135x135 430x135x135\n470x180x103 470x180x103 470x180x103\n410x270x150 390x230x140 345x205x145\n520x350x315 460x400x115 430x360x242\n405x400x380 405x400x380 405x400x380\n485x400x255 410x360x115 330x240x160\n553x261x152 280x230x180 280x230x180\n127x127x127 127x127x127 127x127x127\n220x210x180 285x265x245 580x235x200\n360x340x170 520x330x200 430x135x135\n430x300x200 390x285x140 430x135x135\n473x323x283 473x323x283 430x135x135\n280x230x180 430x135x135 430x135x135\n310x180x100 310x180x100 310x180x100\n390x340x210 185x185x105 185x185x105\n450x290x250 250x200x135 250x200x135\n340x240x170 340x240x170 340x240x170\n470x180x103 470x180x103 470x180x103\n540x400x245 330x190x100 330x190x100\n450x250x160 450x250x160 450x250x160\n400x370x330 520x330x200 585x285x200\n250x200x135 250x200x135 250x200x135\n340x280x190 340x280x190 340x280x190\n127x127x127 127x127x127 127x127x127\n460x335x185 340x300x240 340x300x240\n405x400x380 280x160x120 280x160x120\n390x285x140 390x285x140 390x285x140\n295x190x170 295x190x170 285x265x245\n340x280x190 340x280x190 340x280x190\n590x380x280 590x380x280 590x380x280\n245x235x155 245x235x155 245x235x155\n530x385x250 600x400x120 600x400x120\n540x230x160 540x230x160 540x230x160\n390x365x273 375x365x215 580x235x200\n185x185x105 185x185x105 185x185x105\n368x254x171 349x201x172 368x254x171\n400x370x330 553x261x152 290x210x160\n310x300x210 290x210x160 520x330x200\n445x260x185 445x260x185 445x260x185\n230x90x20 230x90x20 230x90x20 230x90x20\n570x220x35 570x220x35 160x60x60 160x60x60\n125x125x75 125x125x75 125x125x75 125x125x75\n425x230x52 425x230x52 425x230x52 425x230x52\n330x280x80 450x310x38 305x205x25 235x155x70\n500x300x80 500x300x80 500x300x80 500x300x80\n160x150x35 160x150x35 160x150x35 160x150x35\n490x330x90 490x330x90 490x330x90 360x295x185\n490x330x90 490x330x90 180x160x100 180x160x100\n490x330x90 490x330x90 270x240x160 270x240x160\n360x340x170 320x140x70 320x140x70 270x240x160\n480x215x140 500x300x80 500x300x80 440x190x190\n295x190x170 240x225x160 250x150x90 250x150x90\n575x360x310 365x305x280 330x280x80 330x280x80\n480x210x190 480x210x190 305x205x25 305x205x25\n460x335x185 460x335x185 450x310x38 450x310x38\n490x330x90 490x330x90 190x190x120 280x230x180\n298x268x241 298x268x241 380x280x65 280x160x120\n580x320x108 490x330x90 260x230x160 180x160x100\n410x250x50 440x270x100 440x270x100 440x270x100\n270x240x160 380x280x65 298x241x216 349x201x172\n390x285x140 390x285x140 390x285x140 390x140x80\n370x350x340 370x350x340 500x355x315 380x280x65\n473x323x283 473x323x283 473x323x283 553x261x152\n250x205x140 250x205x140 250x205x140 250x205x140\n450x250x160 450x250x160 450x250x160 450x250x160\n470x180x103 470x180x103 470x180x103 580x260x255\n370x290x110 195x150x114 195x150x114 245x165x120\n365x305x280 280x230x180 390x230x140 580x260x255\n290x210x160 430x135x135 430x135x135 585x285x200\n450x290x250 450x290x250 450x290x250 450x290x250\n270x240x160 270x240x160 365x305x280 260x205x135\n450x290x250 430x135x135 430x135x135 430x135x135\n270x210x100 270x210x100 260x230x160 260x230x160\n430x360x242 485x400x255 260x215x170 180x160x100\n270x240x160 270x240x160 180x160x100 180x160x100\n430x300x200 430x135x135 430x135x135 430x135x135\n450x290x250 450x290x250 450x290x250 450x290x250\n298x286x260 390x285x140 430x135x135 280x160x120\n405x400x380 600x400x290 370x350x340 360x340x170\n330x190x100 330x190x100 330x190x100 330x190x100\n360x340x170 390x285x140 520x330x200 430x135x135\n390x380x170 410x270x150 440x190x190 440x190x190\n430x300x200 430x300x200 450x290x250 430x135x135\n553x261x152 390x285x140 390x285x140 430x135x135\n460x335x185 460x335x185 460x335x185 460x335x185\n390x358x265 520x330x320 480x215x140 340x300x240\n280x230x180 390x285x140 520x330x200 430x135x135\n280x230x180 520x330x200 430x135x135 430x135x135\n405x280x195 405x280x195 405x280x195 405x280x195\n390x285x140 190x190x120 280x230x180 280x230x180\n250x200x135 250x200x135 340x280x190 340x280x190\n425x320x300 260x210x160 260x230x160 345x220x130\n473x323x283 473x323x283 473x323x283 473x323x283\n310x300x225 310x300x225 345x220x130 345x220x130\n540x230x160 210x148x100 210x148x100 210x148x100\n390x285x140 520x330x200 430x135x135 430x135x135\n340x240x170 345x205x145 485x400x255 430x360x242\n520x330x320 520x330x320 260x230x160 250x205x140\n450x250x160 450x250x160 450x250x160 450x250x160\n440x370x350 440x370x350 440x370x350 580x235x200\n450x250x160 450x250x160 450x250x160 450x250x160\n430x360x242 485x400x255 260x220x120 320x220x180\n340x280x190 340x280x190 340x280x190 340x280x190\n250x200x135 250x200x135 250x200x135 250x200x135\n480x215x140 345x205x145 345x205x145 345x205x145\n390x290x140 390x290x140 390x290x140 390x290x140\n340x280x190 340x280x190 340x280x190 340x280x190\n250x200x135 250x200x135 250x200x135 250x200x135\n250x200x135 250x200x135 390x380x170 390x380x170\n405x280x195 405x280x195 405x280x195 255x190x175\n475x355x285 475x355x285 475x355x285 475x355x285\n145x75x60 145x75x60 145x75x60 145x75x60 145x75x60\n288x62x43 288x62x43 288x62x43 288x62x43 288x62x43\n295x190x170 160x94x50 160x94x50 160x94x50 160x94x50\n160x94x50 160x94x50 160x94x50 160x94x50 580x235x200\n295x190x170 160x94x50 160x94x50 160x94x50 160x94x50\n145x75x60 145x75x60 145x75x60 260x230x160 260x230x160\n570x220x35 570x220x35 570x220x35 570x220x35 390x285x140\n340x280x190 275x170x40 275x170x40 275x170x40 275x170x40\n380x260x75 380x260x75 345x205x145 410x270x150 425x230x52\n490x330x90 490x330x90 470x180x103 470x180x103 470x180x103\n450x290x250 380x260x75 380x260x75 260x210x160 340x280x120\n270x240x160 520x330x200 310x230x70 260x160x160 490x330x90\n473x323x283 300x245x70 470x390x120 470x390x120 570x220x35\n430x135x135 250x150x90 390x285x140 270x240x160 580x260x255\n500x300x80 430x135x135 430x135x135 250x200x135 260x210x160\n460x335x185 460x335x185 450x290x250 490x330x90 480x215x140\n490x330x90 445x205x120 445x205x120 445x205x120 445x205x120\n195x195x195 195x195x195 195x195x195 195x195x195 195x195x195\n165x60x60 165x60x60 165x60x60 165x60x60 165x60x60 165x60x60\n473x323x283 473x323x283 473x323x283 473x323x283 553x261x152\n390x365x273 375x365x215 295x190x170 580x235x200 580x235x200\n470x180x103 470x180x103 470x180x103 365x305x280 260x205x135\n360x340x170 553x261x152 390x285x140 430x135x135 430x135x135\n340x240x170 340x240x170 340x240x170 250x205x140 250x205x140\n230x53x53 230x53x53 230x53x53 230x53x53 230x53x53 230x53x53\n220x220x220 220x220x220 220x220x220 220x220x220 220x220x220\n473x323x283 473x323x283 473x323x283 430x300x200 390x285x140\n220x220x220 220x220x220 220x220x220 220x220x220 220x220x220\n515x400x265 520x330x200 410x270x150 345x205x145 553x261x152\n480x215x140 390x280x135 180x160x100 345x220x130 390x380x170\n405x400x380 405x400x380 405x400x380 405x400x380 480x215x140\n460x310x310 185x165x100 185x165x100 250x205x140 368x254x171\n260x220x120 260x220x120 260x220x120 260x220x120 260x220x120\n345x220x130 345x220x130 340x280x120 340x280x120 185x165x100\n580x320x108 580x320x108 580x320x108 580x320x108 580x320x108\n298x286x260 317x317x260 440x300x230 470x390x120 470x390x120\n430x300x200 430x300x200 360x340x170 390x285x140 585x285x200\n220x210x180 390x285x140 520x330x200 345x205x145 553x261x152\n520x330x320 330x190x100 390x285x140 430x135x135 430x135x135\n572x292x175 572x292x175 572x292x175 572x292x175 572x292x175\n200x200x200 200x200x200 200x200x200 200x200x200 200x200x200\n405x280x195 405x280x195 405x280x195 405x280x195 405x280x195\n473x323x283 473x323x283 473x323x283 430x300x200 390x285x140\n520x330x320 430x360x242 430x360x242 435x230x230 580x235x200\n280x230x180 280x230x180 520x330x200 430x135x135 430x135x135\n430x300x200 400x370x330 390x285x140 520x330x200 430x135x135\n245x235x155 245x235x155 245x235x155 245x235x155 245x235x155\n235x185x180 235x185x180 240x225x160 240x225x160 240x225x160\n410x270x150 480x205x110 480x205x110 480x205x110 480x205x110\n340x280x190 340x280x190 340x280x190 340x280x190 317x317x260\n520x330x320 430x360x242 430x360x242 435x230x230 580x235x200\n390x365x273 575x360x310 400x400x300 340x240x170 580x390x280\n430x300x200 430x300x200 400x370x330 390x285x140 585x285x200\n430x300x200 400x370x330 553x261x152 290x210x160 390x285x140\n160x94x50 160x94x50 160x94x50 160x94x50 580x235x200 295x190x170\n295x190x170 580x235x200 160x94x50 160x94x50 160x94x50 160x94x50\n300x220x210 300x220x210 450x90x30 450x90x30 450x90x30 450x90x30\n340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40\n298x241x216 490x330x90 490x330x90 490x330x90 490x330x90 425x230x52\n460x335x185 520x330x200 340x280x190 270x210x70 380x280x65 100x80x80\n400x180x110 400x180x110 390x140x80 390x140x80 390x140x80 390x140x80\n375x365x215 295x190x170 285x265x245 280x155x115 160x94x50 160x94x50\n575x360x310 490x330x90 490x330x90 520x330x200 260x205x135 390x230x140\n385x290x150 440x350x280 430x135x135 430x135x135 250x150x90 250x150x90\n473x323x283 473x323x283 470x390x120 470x390x120 145x75x60 430x135x135\n260x200x165 260x160x160 490x330x90 490x330x90 390x285x140 260x155x105\n490x330x90 490x330x90 470x180x103 470x180x103 470x180x103 580x260x255\n590x380x280 590x380x280 590x380x280 590x380x280 590x380x280 320x185x20\n430x300x200 430x300x200 390x285x140 390x285x140 430x135x135 430x135x135\n195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195\n580x390x280 580x390x280 580x390x280 580x390x280 415x390x315 415x390x315\n553x261x152 290x210x160 290x210x160 430x135x135 430x135x135 430x135x135\n400x280x205 310x195x185 560x310x280 345x220x130 345x220x130 298x286x260\n195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195\n430x300x200 360x340x170 553x261x152 290x210x160 390x285x140 430x135x135\n430x300x200 430x300x200 430x300x200 430x300x200 430x135x135 430x135x135\n390x340x210 220x210x180 430x360x242 520x330x200 480x215x140 280x155x115\n220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220\n450x290x250 372x365x219 430x300x200 390x390x230 540x400x245 540x400x245\n195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195\n220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220\n405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195\n473x323x283 473x323x283 390x285x140 520x330x200 430x135x135 430x135x135\n430x300x200 390x285x140 520x330x200 430x135x135 430x135x135 430x135x135\n430x300x200 360x340x170 280x230x180 390x285x140 430x135x135 430x135x135\n450x290x250 480x215x140 480x215x140 480x215x140 340x280x190 340x280x190\n560x310x280 560x310x280 560x310x280 560x310x280 560x310x280 560x310x280\n290x210x160 290x210x160 330x190x100 330x190x100 330x190x100 330x190x100\n372x365x219 430x300x200 430x300x200 360x340x170 360x340x170 410x335x330\n600x320x230 600x320x230 600x320x230 290x210x160 290x210x160 290x210x160\n473x323x283 473x323x283 473x323x283 473x323x283 390x285x140 390x285x140\n160x94x50 160x94x50 160x94x50 160x94x50 580x235x200 580x235x200 295x190x170\n220x210x180 375x365x215 295x190x170 160x94x50 160x94x50 160x94x50 160x94x50\n500x300x80 500x300x80 500x300x80 490x230x60 490x230x60 490x230x60 490x230x60\n340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 365x305x280\n340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 280x230x180\n298x241x216 298x241x216 250x180x90 250x180x90 250x180x90 250x180x90 250x180x90\n318x318x260 318x318x260 330x240x70 330x240x70 330x240x70 330x240x70 330x240x70\n390x260x85 390x260x85 390x260x85 390x260x85 390x260x85 240x225x160 240x225x160\n440x20x10 440x20x10 440x20x10 440x20x10 440x20x10 440x20x10 440x20x10 440x20x10\n553x261x152 490x330x90 490x330x90 490x330x90 345x220x130 250x150x90 180x160x100\n400x250x45 400x250x45 400x250x45 440x370x350 440x370x350 440x370x350 440x370x350\n515x400x265 600x400x290 260x210x160 480x215x140 480x215x140 380x260x75 380x260x75\n490x230x60 490x230x60 480x215x140 250x200x135 250x200x135 250x200x135 250x200x135\n430x360x242 298x241x216 390x285x140 185x165x100 250x200x135 380x280x65 340x280x190\n340x240x170 585x285x200 250x205x140 400x370x330 250x150x90 390x230x140 445x205x120\n270x240x160 270x240x160 470x180x103 470x180x103 470x180x103 280x230x180 580x260x255\n195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 345x220x130\n220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220\n430x300x200 280x230x180 280x230x180 390x285x140 390x285x140 520x330x200 430x135x135\n572x292x175 572x292x175 572x292x175 572x292x175 572x292x175 572x292x175 572x292x175\n473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 390x285x140\n250x200x135 250x200x135 250x200x135 250x200x135 250x200x135 250x200x135 250x200x135\n405x400x380 405x400x380 405x400x380 405x400x380 405x400x380 405x400x380 480x215x140\n180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100\n510x400x200 510x400x200 510x400x200 510x400x200 510x400x200 510x400x200 510x400x200\n500x300x80 500x300x80 500x300x80 500x300x80 490x230x60 490x230x60 490x230x60 490x230x60\n445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18\n345x220x130 345x220x130 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40\n340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 180x160x100 180x160x100\n340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 280x230x180 580x260x255\n340x240x170 340x240x170 340x240x170 235x155x70 235x155x70 235x155x70 500x300x80 500x300x80\n295x190x170 295x190x170 580x235x200 280x155x115 160x94x50 160x94x50 580x235x200 580x235x200\n250x150x90 250x150x90 310x230x70 310x230x70 390x330x210 390x330x210 400x340x210 410x360x210\n410x360x115 410x360x115 410x360x115 410x360x115 470x390x120 470x390x120 235x155x70 105x57x27\n260x155x105 260x155x105 260x155x105 340x280x190 340x280x190 340x280x190 260x230x230 425x230x52\n540x230x160 260x155x105 260x155x100 260x155x105 280x155x115 260x215x170 490x330x90 298x241x216\n473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 310x300x210 290x210x160\n185x165x100 185x165x100 180x160x100 180x160x100 585x285x200 270x240x160 270x240x160 270x240x160\n240x185x180 280x200x185 220x210x180 220x210x180 165x140x130 310x180x100 500x370x120 410x260x110\n450x290x250 450x290x250 345x220x130 345x220x130 345x220x130 195x195x195 195x195x195 195x195x195\n460x310x310 185x165x100 185x165x100 185x165x100 185x165x100 250x200x135 250x200x135 250x200x135\n405x400x380 405x400x380 405x400x380 340x280x190 340x280x190 340x280x190 340x280x190 340x280x190\n270x240x160 270x240x160 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195\n490x330x90 490x330x90 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 365x305x280\n390x365x273 375x365x215 295x190x170 295x190x170 580x235x200 160x94x50 160x94x50 160x94x50 160x94x50\n145x75x60 145x75x60 145x75x60 145x75x60 145x75x60 145x75x60 145x75x60 145x75x60 145x75x60 145x75x60\n240x225x160 230x53x53 230x53x53 230x53x53 230x53x53 230x53x53 230x53x53 230x53x53 230x53x53 230x53x53\n470x180x103 470x180x103 470x180x103 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40\n330x190x100 330x190x100 340x280x120 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50\n490x330x90 490x330x90 520x330x200 180x160x100 180x160x100 280x230x180 280x230x180 280x230x180 390x230x140\n425x320x300 380x260x75 340x280x190 340x280x190 340x280x120 340x280x120 520x330x200 345x220x130 480x210x190\n230x90x20 230x90x20 230x90x20 230x90x20 230x90x20 230x90x20 215x158x155 215x158x155 215x158x155 150x150x65\n298x241x216 260x215x170 250x150x90 260x160x160 260x160x160 520x330x200 260x155x105 260x155x105 340x280x120\n473x323x283 473x323x283 473x323x283 430x300x200 430x300x200 553x261x152 310x300x210 390x285x140 430x135x135\n450x290x250 430x135x135 430x135x135 185x165x100 185x165x100 185x165x100 220x220x220 220x220x220 220x220x220\n195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195\n473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 360x340x170\n220x210x180 260x155x105 340x280x120 340x280x120 190x180x40 190x180x40 190x180x40 190x180x40 190x180x40 190x180x40\n390x285x140 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 260x205x135 280x230x180 580x260x255\n470x180x103 470x180x103 470x180x103 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 280x230x180\n182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 445x205x120 280x230x180 520x330x200 390x285x140\n390x285x140 390x285x140 380x260x75 380x260x75 380x260x75 380x260x75 380x260x75 260x200x165 430x135x135 430x135x135\n260x200x165 260x200x165 390x285x140 390x285x140 430x135x135 250x150x90 310x230x70 300x245x70 300x245x70 240x225x160\n430x300x200 430x300x200 430x300x200 430x300x200 430x300x200 430x300x200 430x300x200 420x180x75 420x180x75 420x180x75\n260x215x170 490x330x90 390x230x140 500x300x80 365x305x280 180x160x100 425x230x52 520x330x200 340x280x120 430x135x135\n450x290x250 245x235x155 245x235x155 340x280x190 270x240x160 270x240x160 480x215x140 480x215x140 380x260x75 380x260x75\n425x320x300 390x285x140 390x230x140 390x230x140 390x230x140 380x280x65 345x205x145 345x205x145 345x205x145 390x380x170\n375x365x215 380x260x75 245x235x155 580x260x255 580x260x255 298x241x216 298x241x216 349x201x172 368x254x171 410x343x110\n349x201x172 430x360x242 368x254x171 185x165x100 180x160x100 180x160x100 390x285x140 375x150x100 380x280x65 240x225x160\n425x320x300 380x280x65 345x205x145 345x205x145 250x65x65 250x65x65 250x65x65 250x65x65 250x65x65 250x65x65 185x140x138\n195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195\n510x330x260 510x330x260 370x350x340 370x350x340 390x380x170 390x380x170 250x200x135 250x200x135 250x200x135 250x200x135\n465x395x255 465x395x255 465x395x255 465x395x255 465x395x255 465x395x255 465x395x255 465x395x255 465x395x255 465x395x255\n195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195\n490x330x90 490x330x90 390x285x140 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 280x230x180 580x260x255\n390x365x273 340x240x170 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 415x390x315 415x390x315\n490x330x90 490x330x90 490x330x90 490x330x90 260x230x160 340x280x190 340x280x190 340x280x190 330x240x160 185x165x100 185x165x100\n260x215x170 345x205x145 345x205x145 380x280x65 250x150x90 250x150x90 250x150x90 185x165x100 185x165x100 180x160x100 180x160x100\n380x260x75 380x260x75 380x260x75 430x135x135 430x135x135 390x285x140 390x285x140 250x200x135 250x200x135 260x210x160 260x210x160\n330x190x100 330x190x100 260x200x165 260x200x165 180x160x100 180x160x100 180x160x100 180x160x100 260x160x160 260x160x160 130x90x80\n473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 473x323x283 430x300x200\n520x375x320 520x375x320 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90\n295x190x170 295x190x170 165x60x60 165x60x60 165x60x60 165x60x60 165x60x60 165x60x60 160x94x50 160x94x50 160x94x50 160x94x50 240x225x160\n185x140x138 185x140x138 485x400x255 220x105x75 330x190x100 250x150x90 490x330x90 490x330x90 310x230x70 310x230x70 310x230x70 340x280x120\n310x300x225 130x65x65 130x65x65 130x65x65 130x65x65 130x65x65 130x65x65 490x330x90 490x330x90 490x330x90 490x330x90 390x230x140 390x230x140\n500x300x80 490x330x90 380x260x75 430x135x135 220x105x75 185x165x100 185x165x100 330x240x160 340x280x190 260x155x105 260x155x105 340x280x120\n553x261x152 330x190x100 190x190x120 490x330x90 490x330x90 490x330x90 260x230x160 250x205x140 390x285x140 260x155x105 340x280x120 340x280x120\n330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 390x280x135 390x280x135 390x280x135 390x195x45 390x195x45\n385x290x150 375x365x215 372x365x219 585x285x200 298x286x260 318x318x260 318x318x260 298x241x216 298x241x216 345x220x130 345x220x130 450x255x55\n540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160\n450x290x250 450x290x250 450x290x250 450x290x250 450x290x250 450x290x250 450x290x250 450x290x250 450x290x250 450x290x250 450x290x250 450x290x250\n410x360x115 410x360x115 410x360x115 410x360x115 410x360x115 410x360x115 410x360x115 410x360x115 410x360x115 298x286x260 318x318x260 317x317x260\n515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265\n310x300x225 298x286x260 318x318x260 240x225x160 260x215x170 390x285x140 345x205x145 345x205x145 185x165x100 245x235x155 340x280x190 260x155x100\n405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195\n220x105x75 310x230x70 230x53x53 230x53x53 230x53x53 230x53x53 230x53x53 230x53x53 230x53x53 230x53x53 230x53x53 245x235x155 430x135x135 430x135x135\n260x230x160 160x160x70 160x160x70 160x160x70 160x160x70 160x160x70 160x160x70 490x330x90 380x260x75 345x220x130 185x165x100 440x190x190 400x370x330\n470x245x210 470x245x210 535x400x180 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 445x205x120 520x330x200 390x140x80 390x140x80\n450x90x30 480x215x140 450x320x200 185x165x100 420x170x85 420x170x85 260x155x105 390x230x140 180x180x115 365x305x280 445x205x120 230x182x170 245x235x155\n370x350x340 310x300x225 390x340x210 260x160x160 330x240x160 380x260x75 340x280x190 410x270x150 490x330x90 490x330x90 260x210x160 260x230x160 310x230x70\n553x261x152 490x330x90 490x330x90 260x210x160 260x230x160 250x205x140 250x205x140 340x280x120 260x205x135 270x240x160 270x240x160 185x140x138 215x158x155\n255x190x175 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105\n515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 375x245x235\n370x350x340 370x350x340 370x350x340 370x350x340 370x350x340 370x350x340 370x350x340 370x350x340 370x350x340 370x350x340 370x350x340 370x350x340 370x350x340\n295x190x170 295x190x170 580x235x200 165x60x60 165x60x60 165x60x60 165x60x60 165x60x60 165x60x60 160x94x50 160x94x50 160x94x50 160x94x50 240x225x160 580x235x200\n390x365x273 220x210x180 220x210x180 220x210x180 220x210x180 220x210x180 220x210x180 220x210x180 220x210x180 375x365x215 580x235x200 153x64x64 153x64x64 153x64x64\n270x240x160 270x240x160 470x180x103 470x180x103 470x180x103 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 180x160x100 180x160x100 580x260x255\n345x220x130 345x220x130 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 365x305x280 190x190x120 190x190x120 280x230x180 280x230x180 390x230x140\n375x365x215 390x340x210 553x261x152 349x201x172 195x150x114 260x200x165 260x200x165 380x260x75 380x260x75 380x260x75 380x260x75 250x205x140 390x230x140 390x230x140\n405x400x380 405x400x380 405x400x380 405x400x380 370x350x340 370x350x340 480x215x140 480x215x140 480x215x140 130x90x80 130x90x80 130x90x80 130x90x80 130x80x15 130x80x15\n185x185x185 185x185x185 185x185x185 185x185x185 185x185x185 185x185x185 210x210x210 210x210x210 210x210x210 210x210x210 210x210x210 210x210x210 210x210x210 210x210x210\n390x365x273 390x365x273 390x365x273 575x360x310 340x240x170 340x240x170 340x240x170 340x240x170 560x310x280 560x310x280 560x310x280 560x310x280 530x385x250 415x390x315\n380x285x280 470x245x210 470x245x210 470x245x210 245x235x155 425x230x52 390x380x170 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 490x330x90 490x330x90\n270x240x160 270x240x160 470x180x103 470x180x103 470x180x103 390x285x140 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 260x205x135 260x205x135 280x230x180\n490x330x90 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 270x240x160 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35\n280x180x95 280x180x95 280x180x95 280x180x95 368x254x171 349x201x172 160x150x35 160x150x35 160x150x35 160x150x35 160x150x35 160x150x35 160x150x35 160x150x35 160x150x35 160x150x35\n340x240x170 340x240x170 340x240x170 340x240x170 340x240x170 340x240x170 340x240x170 340x240x170 340x240x170 340x240x170 340x240x170 340x240x170 340x240x170 340x240x170 340x240x170\n250x200x135 250x200x135 250x200x135 250x200x135 250x200x135 250x200x135 250x80x65 250x80x65 250x200x135 250x200x135 250x200x135 250x200x135 250x80x65 250x80x65 250x80x65 250x80x65\n380x260x75 260x230x160 340x280x190 260x155x105 310x230x70 410x270x150 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 440x190x190 220x200x80 220x200x80 220x200x80\n320x290x160 425x230x52 180x160x100 500x240x40 500x240x40 500x240x40 160x160x70 160x160x70 160x160x70 160x160x70 160x160x70 160x160x70 380x260x75 340x280x190 345x220x130 520x330x200\n490x330x90 490x330x90 490x330x90 490x330x90 520x330x200 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 180x160x100 180x160x100 280x230x180 280x230x180 580x260x255\n425x230x52 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 270x240x160 330x190x100 345x220x130 345x220x130 490x330x90 260x210x160 260x230x160\n310x300x225 310x300x225 310x300x225 310x300x225 310x300x225 500x300x80 500x300x80 500x300x80 490x230x60 490x230x60 490x330x90 490x330x90 490x330x90 490x330x90 260x215x170 260x215x170\n390x365x273 390x365x273 390x365x273 340x240x170 560x310x280 560x310x280 560x310x280 560x310x280 445x205x18 445x205x18 445x205x18 340x180x180 340x180x180 340x180x180 415x390x315 580x235x200\n390x365x273 390x365x273 390x365x273 400x400x300 340x240x170 445x205x18 340x180x180 530x385x250 415x390x315 390x290x95 260x155x120 260x155x120 260x155x120 260x155x120 260x155x120 260x155x120\n380x260x75 490x330x90 345x220x130 520x330x200 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 245x235x155 340x280x120 425x230x52 270x240x160 270x240x160\n260x155x105 260x155x105 340x280x190 340x280x190 430x135x135 430x135x135 445x205x120 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 490x330x90 490x330x90\n390x358x265 340x240x170 207x147x115 207x147x115 207x147x115 235x155x70 235x155x70 520x330x200 310x230x70 310x230x70 310x230x70 445x205x120 295x190x170 295x190x170 280x155x115 240x225x160 240x225x160\n340x240x170 430x135x135 430x135x135 270x240x160 270x240x160 270x240x160 425x230x52 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 260x160x160 345x220x130 260x230x160\n540x230x160 540x230x160 540x230x160 360x340x170 360x340x170 360x340x170 360x340x170 380x260x75 380x260x75 380x260x75 380x260x75 380x260x75 380x260x75 210x148x100 210x148x100 210x148x100 320x140x70 320x140x70\n520x350x315 520x350x315 380x200x150 185x165x100 260x200x165 390x285x140 520x330x200 430x135x135 585x285x200 345x205x145 580x235x200 310x180x100 310x180x100 310x180x100 450x110x90 450x90x30 490x330x90 260x230x160\n300x200x130 180x160x100 180x160x100 185x165x100 185x165x100 230x182x170 440x190x190 440x190x190 260x155x105 260x155x105 340x280x120 340x280x120 340x280x190 330x190x100 330x190x100 345x220x130 130x90x80 130x90x80\n385x290x150 385x290x150 450x290x250 450x290x250 405x400x380 600x400x290 600x400x290 375x245x235 330x190x100 330x190x100 390x280x135 480x215x140 480x215x140 480x215x140 250x150x90 250x150x90 240x225x160 240x225x160\n325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100\n460x335x185 460x335x185 280x200x185 280x200x185 280x200x185 260x230x160 260x230x160 260x230x160 260x230x160 260x230x160 260x230x160 260x230x160 260x230x160 260x230x160 260x230x160 520x330x200 580x235x200 240x225x160\n580x380x270 580x380x270 580x380x270 580x380x270 580x380x270 580x380x270 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240 325x310x240\n460x335x185 460x335x185 460x335x185 460x335x185 380x260x75 380x260x75 490x330x90 490x330x90 520x330x200 345x205x145 305x205x25 305x205x25 305x205x25 305x205x25 305x205x25 305x205x25 245x235x155 230x182x170 340x280x190\n470x180x103 470x180x103 470x180x103 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 190x190x120 180x160x100 180x160x100 280x230x180\n405x280x195 280x200x185 280x200x185 280x200x185 280x200x185 580x235x200 185x185x105 185x185x105 185x185x105 185x185x105 580x235x200 185x185x105 185x185x105 185x185x105 185x185x105 435x240x47 435x240x47 435x240x47 435x240x47\n560x310x280 560x310x280 560x310x280 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 530x385x250 530x385x250 530x385x250 530x385x250 415x390x315 580x235x200\n460x335x185 390x340x210 390x340x210 390x340x210 390x340x210 250x205x140 235x68x68 235x68x68 235x68x68 235x68x68 570x220x35 570x220x35 570x220x35 570x220x35 450x320x200 400x180x110 400x180x110 390x290x95 299x280x31 299x280x31 450x310x38\n540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160\n515x400x265 390x340x210 430x360x242 485x400x255 485x400x255 255x190x175 440x350x280 410x270x150 390x230x140 345x205x145 390x380x170 185x185x105 185x185x105 185x185x105 185x185x105 298x286x260 318x318x260 317x317x260 298x241x216 298x241x216\n380x260x75 380x260x75 380x260x75 260x210x160 160x160x60 160x160x60 160x160x60 160x160x60 160x160x60 160x160x60 490x330x90 490x330x90 270x240x160 270x240x160 260x160x160 340x280x120 340x280x190 340x280x190 230x210x120 270x210x100 440x190x190\n370x350x340 270x210x100 490x330x90 380x260x75 260x210x160 260x230x160 250x205x140 400x250x45 400x250x45 400x250x45 345x205x145 310x230x70 390x285x140 520x330x200 245x235x155 250x200x135 260x155x105 340x280x120 340x280x120 145x75x60 300x220x210\n260x230x160 490x330x90 260x155x105 260x155x105 340x280x190 340x280x190 260x205x135 230x210x120 195x140x35 195x140x35 195x140x35 195x140x35 450x110x90 450x110x90 160x160x60 160x160x60 160x160x60 160x160x60 160x160x60 160x160x60 250x205x140 585x285x200\n260x210x160 260x210x160 160x160x60 160x160x60 160x160x60 160x160x60 160x160x60 160x160x60 260x230x160 260x230x160 160x160x70 160x160x70 160x160x70 160x160x70 160x160x70 160x160x70 340x280x190 340x280x190 340x280x190 340x280x190 340x280x120 340x280x120\n575x360x310 340x240x170 340x240x170 340x240x170 560x310x280 560x310x280 560x310x280 560x310x280 560x310x280 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 580x235x200 470x380x80 470x380x80 470x380x80 470x380x80\n430x300x200 575x325x265 420x180x75 420x180x75 185x165x100 260x200x165 260x160x160 330x240x160 490x330x90 490x330x90 380x260x75 380x260x75 270x240x160 270x240x160 365x305x280 480x215x140 345x220x130 250x200x135 250x205x140 240x225x160 425x230x52 310x230x70\n100x80x80 100x80x80 310x230x70 310x230x70 345x220x130 260x220x120 180x160x100 180x160x100 180x160x100 450x255x55 450x255x55 340x280x120 185x165x100 185x165x100 260x230x160 160x160x70 160x160x70 160x160x70 160x160x70 160x160x70 160x160x70 440x190x190 245x235x155\n580x390x390 580x390x390 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220\n260x155x100 260x155x100 260x155x100 260x155x100 260x155x100 260x155x100 260x155x100 260x155x100 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105\n85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42 85x50x42\n590x270x240 330x190x100 340x280x190 340x280x190 345x220x130 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 310x230x70 310x230x70 185x165x100 425x230x52 270x240x160 520x330x200 445x205x120 410x270x150 390x230x140 345x205x145 345x205x145 400x370x330 585x285x200\n505x335x310 390x285x140 345x220x130 345x220x130 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 340x110x40 260x205x135 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 195x195x195 190x190x120 180x160x100 180x160x100 180x160x100 180x160x100 390x230x140 580x260x255\n540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160\n530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250 530x385x250\n515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265 515x400x265\n180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 290x210x160 290x210x160 290x210x160 380x260x75 380x260x75 380x260x75 380x260x75 380x260x75 380x260x75 380x260x75 380x260x75 250x205x140 250x205x140\n380x260x75 160x160x70 160x160x70 160x160x70 160x160x70 160x160x70 160x160x70 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 340x280x190 180x160x100 480x205x110 480x205x110 480x205x110 480x205x110 330x190x100 177x55x55 177x55x55 177x55x55 177x55x55 177x55x55 177x55x55 390x380x170 270x240x160\n575x325x265 390x260x85 390x260x85 390x260x85 390x260x85 240x185x180 280x200x185 220x210x180 445x205x120 490x330x90 490x330x90 185x165x100 185x165x100 340x280x120 340x280x120 360x295x185 260x155x105 320x220x180 130x90x80 345x220x130 345x220x130 280x160x120 330x190x100 330x190x100 220x105x75 430x135x135 430x135x135 255x190x175\n380x260x75 380x260x75 490x330x90 160x160x70 160x160x70 160x160x70 160x160x70 160x160x70 160x160x70 260x155x105 260x155x105 330x190x100 340x280x190 340x280x190 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 440x20x10 440x20x10 440x20x10 440x20x10 440x20x10 440x20x10 425x230x52 390x230x140 330x240x70 330x240x70\n575x360x310 580x390x280 560x310x280 560x310x280 560x310x280 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 445x205x18 340x180x180 340x180x180 340x180x180 340x180x180 600x400x120 600x400x120 600x400x120 600x400x120 600x400x120 390x290x95 580x235x200\n460x335x185 460x335x185 490x330x90 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 400x370x330 480x215x140\n340x240x170 585x210x205 360x340x170 430x360x242 430x360x242 485x400x255 485x400x255 430x360x242 553x261x152 440x190x190 270x210x100 490x330x90 380x260x75 250x205x140 345x205x145 430x135x135 310x230x70 190x190x120 185x165x100 260x200x165 180x160x100 260x160x160 390x285x140 520x330x200 270x240x160 245x235x155 390x380x170 250x200x135 260x155x105\n490x330x90 490x330x90 380x260x75 380x260x75 380x260x75 260x230x160 260x230x160 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 182x110x50 180x160x100 310x230x70 310x230x70 130x90x80 130x90x80 425x230x52 580x235x200 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 445x205x120 520x330x200 440x190x190\n280x200x185 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 127x127x127 340x280x190 320x220x180 240x225x160 240x225x160 330x330x180\n405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 405x280x195 255x190x175\n370x350x340 360x340x170 575x325x265 390x340x210 185x165x100 185x165x100 185x165x100 185x165x100 260x200x165 260x200x165 260x200x165 260x200x165 260x160x160 260x160x160 260x160x160 260x160x160 330x240x160 330x240x160 330x240x160 330x240x160 340x280x190 340x280x190 340x280x120 340x280x120 340x280x120 340x280x120 580x260x255 580x260x255 390x330x210 390x330x210\n515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 470x390x260 470x390x260 470x390x260 470x390x260 470x390x260 470x390x260 470x390x260 470x390x260 470x390x260 470x390x260 470x390x260\n505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 520x330x200 470x180x103 270x210x70 270x210x70 270x210x70 270x210x70 270x210x70\n425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 425x305x235 420x345x110 420x345x110 420x345x110 420x345x110 420x345x110 420x345x110 420x345x110 420x345x110 420x345x110 420x345x110 420x345x110 410x285x110\n505x335x310 505x335x310 505x335x310 505x335x310 505x335x310 530x400x320 530x400x320 530x400x320 260x205x135 260x205x135 260x205x135 260x205x135 260x205x135 390x285x140 390x285x140 390x140x80 390x140x80 390x140x80 390x140x80 390x140x80 520x330x200 470x180x103 470x180x103 470x180x103 470x180x103 470x180x103 280x230x180 280x230x180 280x230x180 270x210x70 298x241x216 298x241x216 298x241x216\n380x260x75 380x260x75 260x230x160 260x155x105 260x155x105 310x230x70 345x220x130 260x160x160 180x160x100 180x160x100 185x165x100 185x165x100 440x190x190 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 195x140x35 410x270x150 245x235x155 260x205x135 430x135x135 430x135x135 320x220x180\n175x160x100 175x160x100 580x380x270 450x290x250 450x290x250 450x290x250 450x290x250 450x290x250 570x295x180 380x285x280 380x285x280 380x285x280 380x285x280 380x285x280 130x90x80 130x90x80 130x90x80 130x90x80 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 260x200x165 260x200x165 260x200x165 260x200x165 260x200x165 260x200x165 260x200x165 260x200x165 260x200x165 260x200x165 260x200x165 260x200x165\n460x310x310 145x75x60 145x75x60 145x75x60 220x105x75 260x200x165 185x165x100 260x200x165 180x160x100 330x240x160 490x330x90 380x260x75 260x210x160 260x230x160 250x205x140 390x285x140 430x135x135 310x230x70 345x220x130 250x200x135 130x90x80 305x180x75 305x180x75 305x180x75 305x180x75 305x180x75 305x180x75 195x195x180 195x195x180 195x195x180 195x195x180 340x280x190 340x280x120 340x280x120 320x220x180 380x280x65 215x205x105 215x205x105 390x380x170\n470x200x110 470x200x110 470x200x110 470x200x110 470x200x110 470x200x110 470x200x110 470x200x110 470x200x110 470x200x110 470x200x110 470x200x110 470x200x110 460x280x135 460x280x135 460x280x135 460x280x135 460x280x135 460x280x135 460x280x135 460x280x135 460x280x135 460x280x135 460x280x135 460x280x135 460x280x135 450x90x30 450x90x30 450x90x30 450x90x30 450x90x30 440x80x5 440x80x5 450x102x93 450x102x93 450x102x93 450x102x93 450x102x93 438x80x10 438x80x10\n370x350x340 220x105x75 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 315x315x100 315x315x100 260x200x165 260x200x165 260x200x165 180x160x100 180x160x100 330x240x160 330x240x160 490x330x90 490x330x90 390x285x140 390x285x140 270x240x160 270x240x160 270x240x160 245x235x155 430x135x135 430x135x135 310x230x70 310x230x70 310x230x70 250x200x135 250x200x135 130x90x80 440x190x190 440x190x190 260x155x105 260x155x105 360x295x185 295x190x170 298x241x216 298x241x216\n340x240x170 340x240x170 340x240x170 470x255x74 470x255x74 470x255x74 470x255x74 320x290x160 260x215x170 185x165x100 185x165x100 185x165x100 185x165x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 180x160x100 330x240x160 330x240x160 290x210x160 380x260x75 380x260x75 380x260x75 380x260x75 380x260x75 380x260x75 390x285x140 390x285x140 245x235x155 430x135x135 260x155x105 260x155x105 340x280x120 340x280x120 320x220x180 360x295x185 368x254x171 260x230x160 260x230x160 260x230x160 250x205x140 250x205x140\n415x390x315 415x390x315 415x390x315 415x390x315 415x390x315 415x390x315 415x390x315 415x390x315 415x390x315 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 445x260x185 405x365x340 405x365x340 405x365x340 405x365x340 435x240x47 435x240x47 435x240x47 435x240x47 435x240x47 435x240x47 435x240x47 435x240x47\n340x240x170 460x310x310 585x210x205 370x350x340 370x350x340 370x350x340 575x325x265 298x241x216 185x140x138 295x190x170 270x210x100 440x190x190 260x200x165 490x330x90 490x330x90 490x330x90 490x330x90 490x330x90 380x260x75 380x260x75 380x260x75 380x260x75 260x230x160 260x230x160 250x205x140 250x205x140 390x230x140 345x205x145 345x220x130 130x90x80 310x230x70 310x230x70 220x105x75 185x165x100 260x200x165 180x160x100 180x160x100 260x160x160 330x240x160 390x285x140 390x285x140 520x330x200 270x240x160 270x240x160 390x380x170 390x380x170 425x230x52 250x200x135 250x200x135 260x155x105 340x280x120 340x280x120 340x280x120 320x220x180\n590x370x320 590x370x320 405x365x340 553x261x152 432x168x130 432x168x130 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 250x150x90 240x100x30 240x100x30 240x100x30 240x100x30 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 330x190x100 190x180x40 190x180x40 190x180x40 190x180x40 190x180x40 190x180x40 190x180x40 190x180x40 185x165x100 185x165x100 185x165x100 185x165x100 160x160x40 160x160x40 490x330x90 490x330x90 490x330x90 490x330x90\n390x340x210 390x340x210 240x185x180 240x185x180 280x200x185 280x200x185 460x310x310 575x325x265 553x261x152 450x255x55 330x330x180 240x225x160 280x155x115 280x155x115 580x235x200 380x260x75 380x260x75 380x260x75 380x260x75 260x210x160 260x210x160 345x220x130 430x135x135 430x135x135 250x150x90 375x150x100 375x150x100 260x215x170 260x215x170 185x165x100 260x200x165 180x160x100 180x160x100 260x160x160 245x235x155 250x200x135 250x200x135 250x200x135 250x200x135 340x280x190 360x295x185 320x220x180 490x330x90 490x330x90 260x230x160 260x230x160 445x205x120 250x205x140 250x205x140 250x205x140 250x205x140 190x190x120 190x190x120 390x285x140 390x285x140 270x240x160 270x240x160 270x240x160\n510x330x260 425x320x300 505x335x310 440x180x70 440x180x70 310x230x70 345x220x130 345x220x130 345x220x130 250x200x135 250x200x135 250x200x135 250x200x135 250x200x135 250x200x135 250x200x135 250x200x135 305x180x75 305x180x75 195x195x180 195x195x180 195x195x180 260x155x100 260x155x100 260x155x100 260x155x100 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x120 260x155x120 260x155x120 260x155x120 340x280x120 340x280x120 340x280x120 340x280x120 340x280x120 340x280x120 280x230x180 280x230x180 380x280x65 195x195x195 195x195x195 195x195x195 215x205x105 215x205x105 580x235x200 580x235x200 425x230x52 425x230x52 425x230x52 425x230x52 105x57x27 105x57x27 105x57x27 298x286x260 298x286x260 298x286x260 318x318x260 318x318x260 317x317x260 317x317x260\n465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113\n390x390x375 440x360x330 450x290x250 405x400x380 405x365x340 560x310x280 585x210x205 585x210x205 365x343x206 365x343x206 500x355x315 500x355x315 500x355x315 495x355x315 495x355x315 495x355x315 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 290x210x120 515x400x265 515x400x265 340x280x120 340x280x120 340x280x120 340x280x120 375x245x235\n460x202x195 460x330x200 470x200x110 340x180x180 340x180x180 460x335x185 460x335x185 460x335x185 460x335x185 460x335x185 460x335x185 460x335x185 440x360x330 575x360x310 575x360x310 575x360x310 450x290x250 450x290x250 298x286x260 317x317x260 317x317x260 450x255x55 450x255x55 450x255x55 450x255x55 260x230x160 260x230x160 260x230x160 260x230x160 260x230x160 260x230x160 260x200x165 260x200x165 260x200x165 260x200x165 260x200x165 260x200x165 185x165x100 185x165x100 185x165x100 185x165x100 330x240x160 330x240x70 180x160x100 180x160x100 180x160x100 180x160x100 245x235x155 245x235x155 245x235x155 245x235x155 320x290x160 320x290x160 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x100 260x155x100 260x155x100 260x155x100 260x155x100 256x151x10 256x151x10 256x151x10 256x151x10 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 260x155x105 250x150x90 250x150x90 250x150x90 250x150x90 330x190x100 330x190x100 330x190x100\n540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160 540x230x160\n465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 385x155x27 385x155x27 385x155x27 385x155x27 385x155x27 385x155x27 385x155x27 385x155x27 385x155x27 385x155x27 385x155x27 385x155x27 385x155x27 385x155x27 385x155x27\n500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300 500x350x300\n465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 465x225x113 390x340x210 390x340x210 390x340x210 390x340x210 390x340x210 390x340x210 390x340x210 390x340x210 390x340x210 390x340x210 390x340x210 390x340x210 390x340x210 390x340x210 560x310x280 560x310x280 560x310x280 415x390x315 460x335x185 460x335x185 460x335x185 460x335x185 460x335x185 460x335x185 460x335x185 460x335x185 460x335x185 460x335x185 460x335x185 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 515x335x310 410x255x190 410x255x190 410x255x190 410x255x190 410x255x190 410x255x190 410x255x190 410x255x190 410x255x190 410x255x190 410x255x190 410x255x190 295x190x170 460x360x50 460x360x50 460x360x50 460x360x50 460x360x50 460x360x50 460x360x50 460x360x50 345x220x130 345x220x130 580x260x255 580x260x255 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220 220x220x220"
  }
]