# HG changeset patch # User Mike Becker # Date 1471954426 -7200 # Node ID c06ab07fd29dd4809e1d2347e431f60da7526ef4 # Parent 903b46fc42140a30feb6b32208179ac003f53398 increases input buffer + adds regression tests diff -r 903b46fc4214 -r c06ab07fd29d Makefile --- a/Makefile Tue Aug 23 13:59:48 2016 +0200 +++ b/Makefile Tue Aug 23 14:13:46 2016 +0200 @@ -67,5 +67,12 @@ -H test/header.html -F test/footer.html ./build/$(BIN) -p test/plain.csp -o build/plain.html \ -H test/header.html -F test/footer.html + +test-v2-regression: test + diff build/ctest.html test/v2-regression/ctest.html && \ + diff build/javatest.html test/v2-regression/javatest.html && \ + diff build/bigtest.html test/v2-regression/bigtest.html && \ + diff build/plain.html test/v2-regression/plain.html + clean: $(RM) $(RMFLAGS) build diff -r 903b46fc4214 -r c06ab07fd29d src/c2html.c --- a/src/c2html.c Tue Aug 23 13:59:48 2016 +0200 +++ b/src/c2html.c Tue Aug 23 14:13:46 2016 +0200 @@ -244,9 +244,9 @@ FILE *inputfile = fopen(settings.infilename, "r"); if (inputfile) { UcxBuffer *filebuf = ucx_buffer_new(NULL, - 2048, UCX_BUFFER_AUTOEXTEND); + 8192, UCX_BUFFER_AUTOEXTEND); { - const size_t tmpbufsize = 512; + const size_t tmpbufsize = 4096; char *tmpbuf = malloc(tmpbufsize); ucx_stream_copy(inputfile, filebuf, (read_func) fread, (write_func) ucx_buffer_write, diff -r 903b46fc4214 -r c06ab07fd29d test/v2-regression/bigtest.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/v2-regression/bigtest.html Tue Aug 23 14:13:46 2016 +0200 @@ -0,0 +1,855 @@ + + + + c2html + + + + +
+  1  /*
+  2   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+  3   *
+  4   * Copyright 2014 Mike Becker. All rights reserved.
+  5   *
+  6   * Redistribution and use in source and binary forms, with or without
+  7   * modification, are permitted provided that the following conditions are met:
+  8   *
+  9   *   1. Redistributions of source code must retain the above copyright
+ 10   *      notice, this list of conditions and the following disclaimer.
+ 11   *
+ 12   *   2. Redistributions in binary form must reproduce the above copyright
+ 13   *      notice, this list of conditions and the following disclaimer in the
+ 14   *      documentation and/or other materials provided with the distribution.
+ 15   *
+ 16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ 17   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ 18   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ 19   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ 20   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ 21   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ 22   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ 23   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ 24   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ 25   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ 26   * POSSIBILITY OF SUCH DAMAGE.
+ 27   *
+ 28   */
+ 29  
+ 30  #include "rules.h"
+ 31  #include "chess.h"
+ 32  #include <string.h>
+ 33  #include <stdlib.h>
+ 34  #include <sys/time.h>
+ 35  
+ 36  static GameState gamestate_copy_sim(GameState *gamestate) {
+ 37      GameState simulation = *gamestate;
+ 38      if (simulation.lastmove) {
+ 39          MoveList *lastmovecopy = malloc(sizeof(MoveList));
+ 40          *lastmovecopy = *(simulation.lastmove);
+ 41          simulation.movelist = simulation.lastmove = lastmovecopy;
+ 42      }
+ 43  
+ 44      return simulation;
+ 45  }
+ 46  
+ 47  void gamestate_init(GameState *gamestate) {
+ 48      memset(gamestate, 0, sizeof(GameState));
+ 49      
+ 50      Board initboard = {
+ 51          {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
+ 52          {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
+ 53          {0,     0,       0,       0,      0,     0,       0,       0},
+ 54          {0,     0,       0,       0,      0,     0,       0,       0},
+ 55          {0,     0,       0,       0,      0,     0,       0,       0},
+ 56          {0,     0,       0,       0,      0,     0,       0,       0},
+ 57          {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
+ 58          {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
+ 59      };
+ 60      memcpy(gamestate->board, initboard, sizeof(Board));
+ 61  }
+ 62  
+ 63  void gamestate_cleanup(GameState *gamestate) {
+ 64      MoveList *elem;
+ 65      elem = gamestate->movelist;
+ 66      while (elem) {
+ 67          MoveList *cur = elem;
+ 68          elem = elem->next;
+ 69          free(cur);
+ 70      };
+ 71  }
+ 72  
+ 73  /* MUST be called IMMEDIATLY after applying a move to work correctly */
+ 74  static void format_move(GameState *gamestate, Move *move) {
+ 75      char *string = move->string;
+ 76      
+ 77      /* at least 8 characters should be available, wipe them out */
+ 78      memset(string, 0, 8);
+ 79      
+ 80      /* special formats for castling */
+ 81      if ((move->piece&PIECE_MASK) == KING &&
+ 82              abs(move->tofile-move->fromfile) == 2) {
+ 83          if (move->tofile==fileidx('c')) {
+ 84              memcpy(string, "O-O-O", 5);
+ 85          } else {
+ 86              memcpy(string, "O-O", 3);
+ 87          }
+ 88      }
+ 89  
+ 90      /* start by notating the piece character */
+ 91      string[0] = getpiecechr(move->piece);
+ 92      int idx = string[0] ? 1 : 0;
+ 93      
+ 94      /* find out how many source information we do need */
+ 95      uint8_t piece = move->piece & PIECE_MASK;
+ 96      if (piece == PAWN) {
+ 97          if (move->capture) {
+ 98              string[idx++] = filechr(move->fromfile);
+ 99          }
+100      } else if (piece != KING) {
+101          Move threats[16];
+102          uint8_t threatcount;
+103          get_real_threats(gamestate, move->torow, move->tofile,
+104              move->piece&COLOR_MASK, threats, &threatcount);
+105          if (threatcount > 1) {
+106              int ambrows = 0, ambfiles = 0;
+107              for (uint8_t i = 0 ; i < threatcount ; i++) {
+108                  if (threats[i].fromrow == move->fromrow) {
+109                      ambrows++;
+110                  }
+111                  if (threats[i].fromfile == move->fromfile) {
+112                      ambfiles++;
+113                  }
+114              }
+115              /* ambiguous row, name file */
+116              if (ambrows > 1) {
+117                  string[idx++] = filechr(move->fromfile);
+118              }
+119              /* ambiguous file, name row */
+120              if (ambfiles > 1) {
+121                  string[idx++] = filechr(move->fromrow);
+122              }
+123          }
+124      }
+125      
+126      /* capturing? */
+127      if (move->capture) {
+128          string[idx++] = 'x';
+129      }
+130      
+131      /* destination */
+132      string[idx++] = filechr(move->tofile);
+133      string[idx++] = rowchr(move->torow);
+134      
+135      /* promotion? */
+136      if (move->promotion) {
+137          string[idx++] = '=';
+138          string[idx++] = getpiecechr(move->promotion);
+139      }
+140      
+141      /* check? */
+142      if (move->check) {
+143          /* works only, if this function is called when applying the move */
+144          string[idx++] = gamestate->checkmate?'#':'+';
+145      }
+146  }
+147  
+148  static void addmove(GameState* gamestate, Move *move) {
+149      MoveList *elem = malloc(sizeof(MoveList));
+150      elem->next = NULL;
+151      elem->move = *move;
+152      
+153      struct timeval curtimestamp;
+154      gettimeofday(&curtimestamp, NULL);
+155      elem->move.timestamp.tv_sec = curtimestamp.tv_sec;
+156      elem->move.timestamp.tv_usec = curtimestamp.tv_usec;
+157      
+158      if (gamestate->lastmove) {
+159          struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp);
+160          uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec;
+161          suseconds_t micros;
+162          if (curtimestamp.tv_usec < lasttstamp->tv_usec) {
+163              micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec);
+164              sec--;
+165          } else {
+166              micros = curtimestamp.tv_usec - lasttstamp->tv_usec;
+167          }
+168          
+169          elem->move.movetime.tv_sec = sec;
+170          elem->move.movetime.tv_usec = micros;
+171          
+172          gamestate->lastmove->next = elem;
+173          gamestate->lastmove = elem;
+174      } else {
+175          elem->move.movetime.tv_usec = 0;
+176          elem->move.movetime.tv_sec = 0;
+177          gamestate->movelist = gamestate->lastmove = elem;
+178      }
+179  }
+180  
+181  char getpiecechr(uint8_t piece) {
+182      switch (piece & PIECE_MASK) {
+183      case ROOK: return 'R';
+184      case KNIGHT: return 'N';
+185      case BISHOP: return 'B';
+186      case QUEEN: return 'Q';
+187      case KING: return 'K';
+188      default: return '\0';
+189      }
+190  }
+191  
+192  uint8_t getpiece(char c) {
+193      switch (c) {
+194          case 'R': return ROOK;
+195          case 'N': return KNIGHT;
+196          case 'B': return BISHOP;
+197          case 'Q': return QUEEN;
+198          case 'K': return KING;
+199          default: return 0;
+200      }
+201  }
+202  
+203  static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) {
+204      uint8_t piece = move->piece & PIECE_MASK;
+205      uint8_t color = move->piece & COLOR_MASK;
+206      
+207      /* en passant capture */
+208      if (move->capture && piece == PAWN &&
+209          mdst(gamestate->board, move) == 0) {
+210          gamestate->board[move->fromrow][move->tofile] = 0;
+211      }
+212      
+213      /* remove old en passant threats */
+214      for (uint8_t file = 0 ; file < 8 ; file++) {
+215          gamestate->board[3][file] &= ~ENPASSANT_THREAT;
+216          gamestate->board[4][file] &= ~ENPASSANT_THREAT;
+217      }
+218      
+219      /* add new en passant threat */
+220      if (piece == PAWN && (
+221          (move->fromrow == 1 && move->torow == 3) ||
+222          (move->fromrow == 6 && move->torow == 4))) {
+223          move->piece |= ENPASSANT_THREAT;
+224      }
+225      
+226      /* move (and maybe capture or promote) */
+227      msrc(gamestate->board, move) = 0;
+228      if (move->promotion) {
+229          mdst(gamestate->board, move) = move->promotion;
+230      } else {
+231          mdst(gamestate->board, move) = move->piece;
+232      }
+233      
+234      /* castling */
+235      if (piece == KING && move->fromfile == fileidx('e')) {
+236          
+237          if (move->tofile == fileidx('g')) {
+238              gamestate->board[move->torow][fileidx('h')] = 0;
+239              gamestate->board[move->torow][fileidx('f')] = color|ROOK;
+240          } else if (move->tofile == fileidx('c')) {
+241              gamestate->board[move->torow][fileidx('a')] = 0;
+242              gamestate->board[move->torow][fileidx('d')] = color|ROOK;
+243          }
+244      }
+245  
+246      if (!simulate) {
+247          if (!move->string[0]) {
+248              format_move(gamestate, move);
+249          }
+250      }
+251      /* add move, even in simulation (checkmate test needs it) */
+252      addmove(gamestate, move);
+253  }
+254  
+255  void apply_move(GameState *gamestate, Move *move) {
+256      apply_move_impl(gamestate, move, 0);
+257  }
+258  
+259  static int validate_move_rules(GameState *gamestate, Move *move) {
+260      /* validate indices (don't trust opponent) */
+261      if (!chkidx(move)) {
+262          return INVALID_POSITION;
+263      }
+264      
+265      /* must move */
+266      if (move->fromfile == move->tofile && move->fromrow == move->torow) {
+267          return INVALID_MOVE_SYNTAX;
+268      }
+269      
+270      /* does piece exist */
+271      if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK))
+272             != (move->piece&(PIECE_MASK|COLOR_MASK))) {
+273          return INVALID_POSITION;
+274      }
+275      
+276      /* can't capture own pieces */
+277      if ((mdst(gamestate->board, move) & COLOR_MASK)
+278              == (move->piece & COLOR_MASK)) {
+279          return RULES_VIOLATED;
+280      }
+281      
+282      /* must capture, if and only if destination is occupied */
+283      if ((mdst(gamestate->board, move) == 0 && move->capture) ||
+284              (mdst(gamestate->board, move) != 0 && !move->capture)) {
+285          return INVALID_MOVE_SYNTAX;
+286      }
+287      
+288      /* validate individual rules */
+289      _Bool chkrules;
+290      switch (move->piece & PIECE_MASK) {
+291      case PAWN:
+292          chkrules = pawn_chkrules(gamestate, move) &&
+293              !pawn_isblocked(gamestate, move);
+294          break;
+295      case ROOK:
+296          chkrules = rook_chkrules(move) &&
+297              !rook_isblocked(gamestate, move);
+298          break;
+299      case KNIGHT:
+300          chkrules = knight_chkrules(move); /* knight is never blocked */
+301          break;
+302      case BISHOP:
+303          chkrules = bishop_chkrules(move) &&
+304              !bishop_isblocked(gamestate, move);
+305          break;
+306      case QUEEN:
+307          chkrules = queen_chkrules(move) &&
+308              !queen_isblocked(gamestate, move);
+309          break;
+310      case KING:
+311          chkrules = king_chkrules(gamestate, move) &&
+312              !king_isblocked(gamestate, move);
+313          break;
+314      default:
+315          return INVALID_MOVE_SYNTAX;
+316      }
+317      
+318      return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED;
+319  }
+320  
+321  int validate_move(GameState *gamestate, Move *move) {
+322      
+323      int result = validate_move_rules(gamestate, move);
+324      
+325      /* cancel processing to save resources */
+326      if (result != VALID_MOVE_SEMANTICS) {
+327          return result;
+328      }
+329      
+330      /* find kings for check validation */
+331      uint8_t piececolor = (move->piece & COLOR_MASK);
+332      
+333      uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0;
+334      for (uint8_t row = 0 ; row < 8 ; row++) {
+335          for (uint8_t file = 0 ; file < 8 ; file++) {
+336              if (gamestate->board[row][file] ==
+337                      (piececolor == WHITE?WKING:BKING)) {
+338                  mykingfile = file;
+339                  mykingrow = row;
+340              } else if (gamestate->board[row][file] ==
+341                      (piececolor == WHITE?BKING:WKING)) {
+342                  opkingfile = file;
+343                  opkingrow = row;
+344              }
+345          }
+346      }
+347      
+348      /* simulate move for check validation */
+349      GameState simulation = gamestate_copy_sim(gamestate);
+350      Move simmove = *move;
+351      apply_move_impl(&simulation, &simmove, 1);
+352      
+353      /* don't move into or stay in check position */
+354      if (is_covered(&simulation, mykingrow, mykingfile,
+355          opponent_color(piececolor))) {
+356          
+357          gamestate_cleanup(&simulation);
+358          if ((move->piece & PIECE_MASK) == KING) {
+359              return KING_MOVES_INTO_CHECK;
+360          } else {
+361              /* last move is always not null in this case */
+362              return gamestate->lastmove->move.check ?
+363                  KING_IN_CHECK : PIECE_PINNED;
+364          }
+365      }
+366      
+367      /* correct check and checkmate flags (move is still valid) */
+368      Move threats[16];
+369      uint8_t threatcount;
+370      move->check = get_threats(&simulation, opkingrow, opkingfile,
+371          piececolor, threats, &threatcount);
+372      
+373      if (move->check) {
+374          /* determine possible escape fields */
+375          _Bool canescape = 0;
+376          for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
+377              for (int df = -1 ; df <= 1 && !canescape ; df++) {
+378                  if (!(dr == 0 && df == 0)  &&
+379                          isidx(opkingrow + dr) && isidx(opkingfile + df)) {
+380                      
+381                      /* escape field neither blocked nor covered */
+382                      if ((simulation.board[opkingrow + dr][opkingfile + df]
+383                              & COLOR_MASK) != opponent_color(piececolor)) {
+384                          canescape |= !is_covered(&simulation,
+385                              opkingrow + dr, opkingfile + df, piececolor);
+386                      }
+387                  }
+388              }
+389          }
+390          /* can't escape, can he capture? */
+391          if (!canescape && threatcount == 1) {
+392              canescape = is_attacked(&simulation, threats[0].fromrow,
+393                  threats[0].fromfile, opponent_color(piececolor));
+394          }
+395          
+396          /* can't capture, can he block? */
+397          if (!canescape && threatcount == 1) {
+398              Move *threat = &(threats[0]);
+399              uint8_t threatpiece = threat->piece & PIECE_MASK;
+400              
+401              /* knight, pawns and the king cannot be blocked */
+402              if (threatpiece == BISHOP || threatpiece == ROOK
+403                  || threatpiece == QUEEN) {
+404                  if (threat->fromrow == threat->torow) {
+405                      /* rook aspect (on row) */
+406                      int d = threat->tofile > threat->fromfile ? 1 : -1;
+407                      uint8_t file = threat->fromfile;
+408                      while (!canescape && file != threat->tofile - d) {
+409                          file += d;
+410                          canescape |= is_protected(&simulation,
+411                              threat->torow, file, opponent_color(piececolor));
+412                      }
+413                  } else if (threat->fromfile == threat->tofile) {
+414                      /* rook aspect (on file) */
+415                      int d = threat->torow > threat->fromrow ? 1 : -1;
+416                      uint8_t row = threat->fromrow;
+417                      while (!canescape && row != threat->torow - d) {
+418                          row += d;
+419                          canescape |= is_protected(&simulation,
+420                              row, threat->tofile, opponent_color(piececolor));
+421                      }
+422                  } else {
+423                      /* bishop aspect */
+424                      int dr = threat->torow > threat->fromrow ? 1 : -1;
+425                      int df = threat->tofile > threat->fromfile ? 1 : -1;
+426  
+427                      uint8_t row = threat->fromrow;
+428                      uint8_t file = threat->fromfile;
+429                      while (!canescape && file != threat->tofile - df
+430                          && row != threat->torow - dr) {
+431                          row += dr;
+432                          file += df;
+433                          canescape |= is_protected(&simulation, row, file,
+434                              opponent_color(piececolor));
+435                      }
+436                  }
+437              }
+438          }
+439              
+440          if (!canescape) {
+441              gamestate->checkmate = 1;
+442          }
+443      }
+444      
+445      gamestate_cleanup(&simulation);
+446      
+447      return VALID_MOVE_SEMANTICS;
+448  }
+449  
+450  _Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
+451          uint8_t color, Move *threats, uint8_t *threatcount) {
+452      Move candidates[32];
+453      int candidatecount = 0;
+454      for (uint8_t r = 0 ; r < 8 ; r++) {
+455          for (uint8_t f = 0 ; f < 8 ; f++) {
+456              if ((gamestate->board[r][f] & COLOR_MASK) == color) {
+457                  // non-capturing move
+458                  memset(&(candidates[candidatecount]), 0, sizeof(Move));
+459                  candidates[candidatecount].piece = gamestate->board[r][f];
+460                  candidates[candidatecount].fromrow = r;
+461                  candidates[candidatecount].fromfile = f;
+462                  candidates[candidatecount].torow = row;
+463                  candidates[candidatecount].tofile = file;
+464                  candidatecount++;
+465  
+466                  // capturing move
+467                  memcpy(&(candidates[candidatecount]),
+468                      &(candidates[candidatecount-1]), sizeof(Move));
+469                  candidates[candidatecount].capture = 1;
+470                  candidatecount++;
+471              }
+472          }
+473      }
+474  
+475      if (threatcount) {
+476          *threatcount = 0;
+477      }
+478      
+479      
+480      _Bool result = 0;
+481      
+482      for (int i = 0 ; i < candidatecount ; i++) {
+483          if (validate_move_rules(gamestate, &(candidates[i]))
+484                  == VALID_MOVE_SEMANTICS) {
+485              result = 1;
+486              if (threats && threatcount) {
+487                  threats[(*threatcount)++] = candidates[i];
+488              }
+489          }
+490      }
+491      
+492      return result;
+493  }
+494  
+495  _Bool is_pinned(GameState *gamestate, Move *move) {
+496      uint8_t color = move->piece & COLOR_MASK;
+497  
+498      uint8_t kingfile = 0, kingrow = 0;
+499      for (uint8_t row = 0 ; row < 8 ; row++) {
+500          for (uint8_t file = 0 ; file < 8 ; file++) {
+501              if (gamestate->board[row][file] == (color|KING)) {
+502                  kingfile = file;
+503                  kingrow = row;
+504              }
+505          }
+506      }
+507  
+508      GameState simulation = gamestate_copy_sim(gamestate);
+509      Move simmove = *move;
+510      apply_move(&simulation, &simmove);
+511      _Bool covered = is_covered(&simulation,
+512          kingrow, kingfile, opponent_color(color));
+513      gamestate_cleanup(&simulation);
+514      
+515      return covered;
+516  }
+517  
+518  _Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file,
+519          uint8_t color, Move *threats, uint8_t *threatcount) {
+520      
+521      if (threatcount) {
+522          *threatcount = 0;
+523      }
+524  
+525      Move candidates[16];
+526      uint8_t candidatecount;
+527      if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
+528          
+529          _Bool result = 0;
+530          uint8_t kingfile = 0, kingrow = 0;
+531          for (uint8_t row = 0 ; row < 8 ; row++) {
+532              for (uint8_t file = 0 ; file < 8 ; file++) {
+533                  if (gamestate->board[row][file] == (color|KING)) {
+534                      kingfile = file;
+535                      kingrow = row;
+536                  }
+537              }
+538          }
+539  
+540          for (uint8_t i = 0 ; i < candidatecount ; i++) {
+541              GameState simulation = gamestate_copy_sim(gamestate);
+542              Move simmove = candidates[i];
+543              apply_move(&simulation, &simmove);
+544              if (!is_covered(&simulation, kingrow, kingfile,
+545                      opponent_color(color))) {
+546                  result = 1;
+547                  if (threats && threatcount) {
+548                      threats[(*threatcount)++] = candidates[i];
+549                  }
+550              }
+551          }
+552          
+553          return result;
+554      } else {
+555          return 0;
+556      }
+557  }
+558  
+559  static int getlocation(GameState *gamestate, Move *move) {   
+560  
+561      uint8_t color = move->piece & COLOR_MASK;
+562      _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0;
+563      
+564      Move threats[16], *threat = NULL;
+565      uint8_t threatcount;
+566      
+567      if (get_threats(gamestate, move->torow, move->tofile, color,
+568              threats, &threatcount)) {
+569          
+570          int reason = INVALID_POSITION;
+571          
+572          // find threats for the specified position
+573          for (uint8_t i = 0 ; i < threatcount ; i++) {
+574              if ((threats[i].piece & (PIECE_MASK | COLOR_MASK))
+575                      == move->piece &&
+576                      (move->fromrow == POS_UNSPECIFIED ||
+577                      move->fromrow == threats[i].fromrow) &&
+578                      (move->fromfile == POS_UNSPECIFIED ||
+579                      move->fromfile == threats[i].fromfile)) {
+580  
+581                  if (threat) {
+582                      return AMBIGUOUS_MOVE;
+583                  } else {
+584                      // found threat is no real threat
+585                      if (is_pinned(gamestate, &(threats[i]))) {
+586                          reason = incheck?KING_IN_CHECK:PIECE_PINNED;
+587                      } else {
+588                          threat = &(threats[i]);
+589                      }
+590                  }
+591              }
+592          }
+593          
+594          // can't threaten specified position
+595          if (!threat) {
+596              return reason;
+597          }
+598  
+599          memcpy(move, threat, sizeof(Move));
+600          return VALID_MOVE_SYNTAX;
+601      } else {
+602          return INVALID_POSITION;
+603      }
+604  }
+605  
+606  int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
+607      memset(move, 0, sizeof(Move));
+608      move->fromfile = POS_UNSPECIFIED;
+609      move->fromrow = POS_UNSPECIFIED;
+610  
+611      size_t len = strlen(mstr);
+612      if (len < 1 || len > 6) {
+613          return INVALID_MOVE_SYNTAX;
+614      }
+615      
+616      /* evaluate check/checkmate flags */
+617      if (mstr[len-1] == '+') {
+618          len--; mstr[len] = '\0';
+619          move->check = 1;
+620      } else if (mstr[len-1] == '#') {
+621          len--; mstr[len] = '\0';
+622          /* ignore - validation should set game state */
+623      }
+624      
+625      /* evaluate promotion */
+626      if (len > 3 && mstr[len-2] == '=') {
+627          move->promotion = getpiece(mstr[len-1]);
+628          if (!move->promotion) {
+629              return INVALID_MOVE_SYNTAX;
+630          } else {
+631              move->promotion |= color;
+632              len -= 2;
+633              mstr[len] = 0;
+634          }
+635      }
+636      
+637      if (len == 2) {
+638          /* pawn move (e.g. "e4") */
+639          move->piece = PAWN;
+640          move->tofile = fileidx(mstr[0]);
+641          move->torow = rowidx(mstr[1]);
+642      } else if (len == 3) {
+643          if (strcmp(mstr, "O-O") == 0) {
+644              /* king side castling */
+645              move->piece = KING;
+646              move->fromfile = fileidx('e');
+647              move->tofile = fileidx('g');
+648              move->fromrow = move->torow = color == WHITE ? 0 : 7;
+649          } else {
+650              /* move (e.g. "Nf3") */
+651              move->piece = getpiece(mstr[0]);
+652              move->tofile = fileidx(mstr[1]);
+653              move->torow = rowidx(mstr[2]);
+654          }
+655      } else if (len == 4) {
+656          move->piece = getpiece(mstr[0]);
+657          if (!move->piece) {
+658              move->piece = PAWN;
+659              move->fromfile = fileidx(mstr[0]);
+660          }
+661          if (mstr[1] == 'x') {
+662              /* capture (e.g. "Nxf3", "dxe5") */
+663              move->capture = 1;
+664          } else {
+665              /* move (e.g. "Ndf3", "N2c3", "e2e4") */
+666              if (isfile(mstr[1])) {
+667                  move->fromfile = fileidx(mstr[1]);
+668                  if (move->piece == PAWN) {
+669                      move->piece = 0;
+670                  }
+671              } else {
+672                  move->fromrow = rowidx(mstr[1]);
+673              }
+674          }
+675          move->tofile = fileidx(mstr[2]);
+676          move->torow = rowidx(mstr[3]);
+677      } else if (len == 5) {
+678          if (strcmp(mstr, "O-O-O") == 0) {
+679              /* queen side castling "O-O-O" */
+680              move->piece = KING;
+681              move->fromfile = fileidx('e');
+682              move->tofile = fileidx('c');
+683              move->fromrow = move->torow = color == WHITE ? 0 : 7;
+684          } else {
+685              move->piece = getpiece(mstr[0]);
+686              if (mstr[2] == 'x') {
+687                  move->capture = 1;
+688                  if (move->piece) {
+689                      /* capture (e.g. "Ndxf3") */
+690                      move->fromfile = fileidx(mstr[1]);
+691                  } else {
+692                      /* long notation capture (e.g. "e5xf6") */
+693                      move->piece = PAWN;
+694                      move->fromfile = fileidx(mstr[0]);
+695                      move->fromrow = rowidx(mstr[1]);
+696                  }
+697              } else {
+698                  /* long notation move (e.g. "Nc5a4") */
+699                  move->fromfile = fileidx(mstr[1]);
+700                  move->fromrow = rowidx(mstr[2]);
+701              }
+702              move->tofile = fileidx(mstr[3]);
+703              move->torow = rowidx(mstr[4]);
+704          }
+705      } else if (len == 6) {
+706          /* long notation capture (e.g. "Nc5xf3") */
+707          if (mstr[3] == 'x') {
+708              move->capture = 1;
+709              move->piece = getpiece(mstr[0]);
+710              move->fromfile = fileidx(mstr[1]);
+711              move->fromrow = rowidx(mstr[2]);
+712              move->tofile = fileidx(mstr[4]);
+713              move->torow = rowidx(mstr[5]);
+714          }
+715      }
+716  
+717      
+718      if (move->piece) {
+719          if (move->piece == PAWN
+720              && move->torow == (color==WHITE?7:0)
+721              && !move->promotion) {
+722              return NEED_PROMOTION;
+723          }
+724          
+725          move->piece |= color;
+726          if (move->fromfile == POS_UNSPECIFIED
+727              || move->fromrow == POS_UNSPECIFIED) {
+728              return getlocation(gamestate, move);
+729          } else {
+730              return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
+731          }
+732      } else {
+733          return INVALID_MOVE_SYNTAX;
+734      }
+735  }
+736  
+737  _Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file,
+738          uint8_t color) {
+739      
+740      Move threats[16];
+741      uint8_t threatcount;
+742      if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
+743          for (int i = 0 ; i < threatcount ; i++) {
+744              if (threats[i].piece != (color|KING)) {
+745                  return 1;
+746              }
+747          }
+748          return 0;
+749      } else {
+750          return 0;
+751      }
+752  }
+753  
+754  uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate,
+755          uint8_t color) {
+756      if (!gameinfo->timecontrol) {
+757          return 0;
+758      }
+759      
+760      if (gamestate->movelist) {
+761          uint16_t time = gameinfo->time;
+762          suseconds_t micros = 0;
+763          
+764          MoveList *movelist = color == WHITE ?
+765              gamestate->movelist : gamestate->movelist->next;
+766          
+767          while (movelist) {
+768              time += gameinfo->addtime;
+769              
+770              struct movetimeval *movetime = &(movelist->move.movetime);
+771              if (movetime->tv_sec >= time) {
+772                  return 0;
+773              }
+774              
+775              time -= movetime->tv_sec;
+776              micros += movetime->tv_usec;
+777              
+778              movelist = movelist->next ? movelist->next->next : NULL;
+779          }
+780          
+781          time_t sec;
+782          movelist = gamestate->lastmove;
+783          if ((movelist->move.piece & COLOR_MASK) != color) {
+784              struct movetimeval *lastmovetstamp = &(movelist->move.timestamp);
+785              struct timeval currenttstamp;
+786              gettimeofday(¤ttstamp, NULL);
+787              micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec;
+788              sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec;
+789              if (sec >= time) {
+790                  return 0;
+791              }
+792              
+793              time -= sec;
+794          }
+795          
+796          sec = micros / 1e6L;
+797          
+798          if (sec >= time) {
+799              return 0;
+800          }
+801  
+802          time -= sec;
+803          
+804          return time;
+805      } else {
+806          return gameinfo->time;
+807      }
+808  }
+
+ + + diff -r 903b46fc4214 -r c06ab07fd29d test/v2-regression/ctest.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/v2-regression/ctest.html Tue Aug 23 14:13:46 2016 +0200 @@ -0,0 +1,438 @@ + + + + c2html + + + + +
+  1  /*
+  2   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+  3   *
+  4   * Copyright 2015 Olaf Wintermann. All rights reserved.
+  5   *
+  6   * Redistribution and use in source and binary forms, with or without
+  7   * modification, are permitted provided that the following conditions are met:
+  8   *
+  9   *   1. Redistributions of source code must retain the above copyright
+ 10   *      notice, this list of conditions and the following disclaimer.
+ 11   *
+ 12   *   2. Redistributions in binary form must reproduce the above copyright
+ 13   *      notice, this list of conditions and the following disclaimer in the
+ 14   *      documentation and/or other materials provided with the distribution.
+ 15   *
+ 16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ 17   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ 18   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ 19   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ 20   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ 21   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ 22   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ 23   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ 24   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ 25   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ 26   * POSSIBILITY OF SUCH DAMAGE.
+ 27   */
+ 28  
+ 29  #include <time.h>
+ 30  #include <stdio.h>
+ 31  #include <stdlib.h>
+ 32  #include <string.h>
+ 33  #include <ucx/string.h>
+ 34  #include <ucx/buffer.h>
+ 35  #include <ucx/utils.h>
+ 36  #include <libxml/tree.h>
+ 37  #include <curl/curl.h>
+ 38  
+ 39  #include <openssl/sha.h>
+ 40  #include <openssl/hmac.h>
+ 41  #include <openssl/evp.h>
+ 42  #include <openssl/bio.h>
+ 43  #include <openssl/buffer.h>
+ 44  #include <openssl/rand.h>
+ 45  
+ 46  #include "utils.h"
+ 47  #include "crypto.h"
+ 48  #include "webdav.h"
+ 49  
+ 50  #define MACRO1337 1337L
+ 51  
+ 52  /* -------------------- This is a testing file. -------------------------- */
+ 53  /*
+ 54  time_t util_parse_creationdate(char *str) {
+ 55      // example: 2012-11-29T21:35:35Z
+ 56      if(!str) {
+ 57          return 0;
+ 58      }
+ 59      // TODO
+ 60      return 0;
+ 61  }
+ 62  */
+ 63  time_t util_parse_lastmodified(char *str) {
+ 64      // example: Thu, 29 Nov 2012 21:35:35 GMT
+ 65      if(!str) {
+ 66          return 0;
+ 67      } else {
+ 68          return curl_getdate(str, NULL);
+ 69      }
+ 70  }
+ 71  
+ 72  int util_getboolean(char *v) {
+ 73      if(v[0] == 'T' || v[0] == 't') {
+ 74          return 1;
+ 75      }
+ 76      return 0;
+ 77  }
+ 78  
+ 79  int util_strtoint(char *str, int64_t *value) {
+ 80      char *end;
+ 81      int64_t val = strtoll(str, &end, 0);
+ 82      if(strlen(end) == 0) {
+ 83          *value = val;
+ 84          return 1;
+ 85      } else {
+ 86          return 0;
+ 87      }
+ 88  }
+ 89  
+ 90  char* util_url_path(char *url) { 
+ 91      char *path = NULL;
+ 92      size_t len = strlen(url);
+ 93      int slashcount = 0;
+ 94      int slmax;
+ 95      if(len > 7 && !strncasecmp(url, "http://", 7)) {
+ 96          slmax = 3;
+ 97      } else if(len > 8 && !strncasecmp(url, "https://", 8)) {
+ 98          slmax = 3;
+ 99      } else {
+100          slmax = 1;
+101      }
+102      char c;
+103      for(int i=0;i<len;i++) {
+104          c = url[i];
+105          if(c == '/') {
+106              slashcount++;
+107              if(slashcount == slmax) {
+108                  path = url + i;
+109                  break;
+110              }
+111          }
+112      } 
+113      return path;
+114  }
+115  
+116  char* util_url_decode(DavSession *sn, char *url) {
+117      char *unesc = curl_easy_unescape(sn->handle, url, strlen(url), NULL);
+118      char *ret = strdup(unesc);
+119      curl_free(unesc);
+120      return ret;
+121  }
+122  
+123  char* util_resource_name(char *url) {
+124      int si = 0;
+125      int osi = 0;
+126      int i = 0;
+127      int p = 0;
+128      char c;
+129      while((c = url[i]) != 0) {
+130          if(c == '/') {
+131              osi = si;
+132              si = i;
+133              p = 1;
+134          }
+135          i++;
+136      }
+137      
+138      char *name = url + si + p;
+139      if(name[0] == 0) {
+140          name = url + osi + p;
+141          if(name[0] == 0) {
+142              return url;
+143          }
+144      }
+145      
+146      return name;
+147  }
+148  
+149  int util_mkdir(char *path, mode_t mode) {
+150  #ifdef _WIN32
+151      return mkdir(path);
+152  #else
+153      return mkdir(path, mode);
+154  #endif
+155  }
+156  
+157  char* util_concat_path(char *url_base, char *p) {
+158      sstr_t base = sstr(url_base);
+159      sstr_t path;
+160      if(p) {
+161          path = sstr(p);
+162      } else {
+163          path = sstrn("", 0);
+164      }
+165      
+166      int add_separator = 0;
+167      if(base.ptr[base.length-1] == '/') {
+168          if(path.ptr[0] == '/') {
+169              base.length--;
+170          }
+171      } else {
+172          if(path.length == 0 || path.ptr[0] != '/') {
+173              add_separator = 1;
+174          }
+175      }
+176      
+177      sstr_t url;
+178      if(add_separator) {
+179          url = sstrcat(3, base, sstr("/"), path);
+180      } else {
+181          url = sstrcat(2, base, path);
+182      }
+183      
+184      return url.ptr;
+185  }
+186  
+187  void util_set_url(DavSession *sn, char *href) {
+188      sstr_t base = sstr(sn->base_url);
+189      sstr_t href_str = sstr(href);
+190      
+191      char *base_path = util_url_path(sn->base_url);
+192      base.length -= strlen(base_path);
+193      
+194      sstr_t url = sstrcat(2, base, href_str);
+195      
+196      curl_easy_setopt(sn->handle, CURLOPT_URL, url.ptr);
+197      free(url.ptr);
+198  }
+199  
+200  char* util_path_to_url(DavSession *sn, char *path) {
+201      char *space = malloc(256);
+202      UcxBuffer *url = ucx_buffer_new(space, 256, UCX_BUFFER_AUTOEXTEND);
+203      
+204      // add base url
+205      ucx_buffer_write(sn->base_url, 1, strlen(sn->base_url), url);
+206      // remove trailing slash
+207      ucx_buffer_seek(url, -1, SEEK_CUR);
+208      
+209      sstr_t p = sstr(path);
+210      ssize_t ntk = 0;
+211      sstr_t *tks = sstrsplit(p, S("/"), &ntk);
+212      
+213      for(int i=0;i<ntk;i++) {
+214          sstr_t node = tks[i];
+215          if(node.length > 0) {
+216              char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
+217              ucx_buffer_putc(url, '/');
+218              ucx_buffer_write(esc, 1, strlen(esc), url);
+219              curl_free(esc);
+220          }
+221          free(node.ptr);
+222      }
+223      free(tks);
+224      if(path[p.length-1] == '/') {
+225          ucx_buffer_putc(url, '/');
+226      }
+227      ucx_buffer_putc(url, 0);
+228      
+229      space = url->space;
+230      ucx_buffer_free(url);
+231      
+232      return space;
+233  }
+234  
+235  char* util_parent_path(char *path) {
+236      char *name = util_resource_name(path);
+237      size_t namelen = strlen(name);
+238      size_t pathlen = strlen(path);
+239      size_t parentlen = pathlen - namelen;
+240      char *parent = malloc(parentlen + 1);
+241      memcpy(parent, path, parentlen);
+242      parent[parentlen] = '\0';
+243      return parent;
+244  }
+245  
+246  
+247  char* util_xml_get_text(xmlNode *elm) {
+248      xmlNode *node = elm->children;
+249      while(node) {
+250          if(node->type == XML_TEXT_NODE) {
+251              return (char*)node->content;
+252          }
+253          node = node->next;
+254      }
+255      return NULL;
+256  }
+257  
+258  
+259  char* util_base64decode(char *in) {
+260      int len = 0;
+261      return util_base64decode_len(in, &len);
+262  }
+263  
+264  char* util_base64decode_len(char* in, int *outlen) {
+265      size_t len = strlen(in);
+266      char *out = calloc(1, len);
+267      
+268      BIO* b = BIO_new_mem_buf(in, len);
+269      BIO *d = BIO_new(BIO_f_base64());
+270      BIO_set_flags(d, BIO_FLAGS_BASE64_NO_NL);
+271      b = BIO_push(d, b);
+272  
+273      *outlen = BIO_read(b, out, len);
+274      BIO_free_all(b);
+275      
+276      return out;
+277  }
+278  
+279  char* util_base64encode(char *in, size_t len) { 
+280      BIO *b;
+281      BIO *e;
+282      BUF_MEM *mem;
+283  
+284      e = BIO_new(BIO_f_base64());
+285      b = BIO_new(BIO_s_mem());
+286      
+287      e = BIO_push(e, b);
+288      BIO_write(e, in, len);
+289      BIO_flush(e);
+290      
+291      BIO_get_mem_ptr(e, &mem);
+292      char *out = malloc(mem->length);
+293      memcpy(out, mem->data, mem->length -1);
+294      out[mem->length - 1] = '\0';
+295  
+296      BIO_free_all(e);
+297  
+298      return out;
+299  }
+300  
+301  char* util_encrypt_str(DavSession *sn, char *str, char *key) {
+302      DavKey *k = dav_context_get_key(sn->context, key);
+303      if(!k) {
+304          // TODO: session error
+305          return NULL;
+306      }
+307      
+308      char *enc_str = aes_encrypt(str, k);
+309      char *ret_str = dav_session_strdup(sn, enc_str);
+310      free(enc_str);
+311      return ret_str;
+312  }
+313  
+314  /* commented out for testing reasons */
+315  /*
+316  char* util_decrypt_str(DavSession *sn, char *str, char *key) {
+317      DavKey *k = dav_context_get_key(sn->context, key);
+318      if(!k) {
+319          // TODO: session error
+320          return NULL;
+321      }
+322      
+323      char *dec_str = aes_decrypt(str, k);
+324      char *ret_str = dav_session_strdup(sn, dec_str);
+325      free(dec_str);
+326      return ret_str;
+327  }
+328  */
+329  char* util_random_str() {
+330      unsigned char *str = malloc(25);
+331      str[24] = '\0';
+332      
+333      sstr_t t = S(
+334              "01234567890"
+335              "abcdefghijklmnopqrstuvwxyz"
+336              "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+337      const unsigned char *table = (const unsigned char*)t.ptr;
+338      
+339      RAND_pseudo_bytes(str, 24);
+340      for(int i=0;i<24;i++) {
+341          int c = str[i] % t.length;
+342          str[i] = table[c];
+343      }
+344      
+345      return (char*)str;
+346  }
+347  
+348  /*
+349   * gets a substring from 0 to the appearance of the token
+350   * tokens are separated by space
+351   * sets sub to the substring and returns the remaining string
+352   */
+353  sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) {  
+354      int i;
+355      int token_start = -1;
+356      int token_end = -1;
+357      for(i=0;i<=str.length;i++) {
+358          int c;
+359          if(i == str.length) {
+360              c = ' ';
+361          } else {
+362              c = str.ptr[i];
+363          }
+364          if(c < 33) {
+365              if(token_start != -1) {
+366                  token_end = i;
+367                  size_t len = token_end - token_start;
+368                  sstr_t tk = sstrsubsl(str, token_start, len);
+369                  //printf("token: {%.*s}\n", token.length, token.ptr);
+370                  if(!sstrcmp(tk, token)) {
+371                      *sub = sstrtrim(sstrsubsl(str, 0, token_start));
+372                      break;
+373                  }
+374                  token_start = -1;
+375                  token_end = -1;
+376              }
+377          } else {
+378              if(token_start == -1) {
+379                  token_start = i;
+380              }
+381          }
+382      }
+383      
+384      if(i < str.length) {
+385          return sstrtrim(sstrsubs(str, i));
+386      } else {
+387          str.ptr = NULL;
+388          str.length = 0;
+389          return str;
+390      }
+391  }
+
+ + + diff -r 903b46fc4214 -r c06ab07fd29d test/v2-regression/javatest.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/v2-regression/javatest.html Tue Aug 23 14:13:46 2016 +0200 @@ -0,0 +1,215 @@ + + + + c2html + + + + +
+  1  /*
+  2   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+  3   *
+  4   * Copyright 2014 Mike Becker. All rights reserved.
+  5   *
+  6   * Redistribution and use in source and binary forms, with or without
+  7   * modification, are permitted provided that the following conditions are met:
+  8   *
+  9   *   1. Redistributions of source code must retain the above copyright
+ 10   *      notice, this list of conditions and the following disclaimer.
+ 11   *
+ 12   *   2. Redistributions in binary form must reproduce the above copyright
+ 13   *      notice, this list of conditions and the following disclaimer in the
+ 14   *      documentation and/or other materials provided with the distribution.
+ 15   *
+ 16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ 17   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ 18   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ 19   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ 20   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ 21   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ 22   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ 23   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ 24   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ 25   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ 26   * POSSIBILITY OF SUCH DAMAGE.
+ 27   *
+ 28   */
+ 29  
+ 30  package de.uapcore.sigred.doc.base;
+ 31  
+ 32  import de.uapcore.sigred.doc.Resources;
+ 33  import de.uapcore.sigrapi.impl.Digraph;
+ 34  import de.uapcore.sigrapi.impl.Graph;
+ 35  import de.uapcore.sigrapi.IGraph;
+ 36  import java.io.IOException;
+ 37  import java.io.InputStream;
+ 38  import java.io.OutputStream;
+ 39  import java.util.concurrent.atomic.AtomicBoolean;
+ 40  import java.util.concurrent.atomic.AtomicReference;
+ 41  import org.apache.xerces.impl.Constants;
+ 42  import org.dom4j.Document;
+ 43  import org.dom4j.DocumentException;
+ 44  import org.dom4j.DocumentHelper;
+ 45  import org.dom4j.Element;
+ 46  import org.dom4j.Namespace;
+ 47  import org.dom4j.QName;
+ 48  import org.dom4j.io.OutputFormat;
+ 49  import org.dom4j.io.SAXReader;
+ 50  import org.dom4j.io.XMLWriter;
+ 51  import org.xml.sax.ErrorHandler;
+ 52  import org.xml.sax.SAXException;
+ 53  import org.xml.sax.SAXParseException;
+ 54  
+ 55  public abstract class AbstractGraphDocument<T extends IGraph>
+ 56          extends FileBackedDocument {
+ 57      
+ 58      protected static final Namespace NAMESPACE = Namespace.get("sigred",
+ 59          "http://develop.uap-core.de/sigred/");
+ 60      
+ 61      private static final
+ 62          QName TAG_GRAPHDOC = QName.get("graph-document", NAMESPACE);
+ 63      private static final
+ 64          QName TAG_GRAPH = QName.get("graph", NAMESPACE);
+ 65      private static final
+ 66          QName TAG_DIGRAPH = QName.get("digraph", NAMESPACE);
+ 67      private static final
+ 68          QName TAG_METADATA = QName.get("metadata", NAMESPACE);
+ 69      
+ 70      protected final T graph;
+ 71      
+ 72      private final GraphDocumentMetadata metadata;
+ 73      
+ 74      public AbstractGraphDocument(Class<T> graphType) {
+ 75          T g;
+ 76          try {
+ 77              g = graphType.newInstance();
+ 78          } catch (ReflectiveOperationException e) {
+ 79              assert false;
+ 80              g = null; // for the compiler
+ 81          }
+ 82          graph = g;
+ 83          metadata = new GraphDocumentMetadata();
+ 84      }
+ 85  
+ 86      public T getGraph() {
+ 87          return graph;
+ 88      }
+ 89      
+ 90      public GraphDocumentMetadata getMetadata() {
+ 91          return metadata;
+ 92      }
+ 93  
+ 94      protected abstract void writeGraph(Element rootNode) throws IOException;
+ 95      protected abstract void readGraph(Element rootNode) throws IOException;
+ 96  
+ 97      @Override
+ 98      public void writeTo(OutputStream out) throws IOException {
+ 99          Document doc = DocumentHelper.createDocument();
+100  
+101          Element rootNode = doc.addElement(TAG_GRAPHDOC);
+102  
+103          Element metadataNode = rootNode.addElement(TAG_METADATA);
+104  
+105          metadata.write(metadataNode);
+106  
+107          if (graph instanceof Graph) {
+108              writeGraph(rootNode.addElement(TAG_GRAPH));
+109          } else if (graph instanceof Digraph) {
+110              writeGraph(rootNode.addElement(TAG_DIGRAPH));
+111          } else {
+112              throw new IOException("unsupported graph type");
+113          }
+114  
+115          XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
+116          writer.write(doc);
+117          writer.flush();
+118      }
+119  
+120      @Override
+121      public void readFrom(InputStream in) throws IOException {
+122          try {
+123              SAXReader reader = new SAXReader(true);
+124              reader.setStripWhitespaceText(true);
+125              
+126              reader.setFeature(Constants.XERCES_FEATURE_PREFIX+
+127                  Constants.SCHEMA_VALIDATION_FEATURE, true);
+128              reader.setProperty(Constants.XERCES_PROPERTY_PREFIX +
+129                  Constants.SCHEMA_LOCATION, String.format("%s %s",
+130                      NAMESPACE.getURI(), Resources.class.getResource(
+131                          "graph-document.xsd").toExternalForm()));
+132              
+133              final AtomicBoolean passed = new AtomicBoolean(true);
+134              final AtomicReference<SAXParseException> xmlerror = new AtomicReference<>();
+135              // TODO: we should do more detailed error handling here
+136              reader.setErrorHandler(new ErrorHandler() {
+137                  @Override
+138                  public void warning(SAXParseException exception) throws SAXException {
+139                  }
+140  
+141                  @Override
+142                  public void error(SAXParseException exception) throws SAXException {
+143                      xmlerror.set(exception);
+144                      passed.set(false);
+145                  }
+146  
+147                  @Override
+148                  public void fatalError(SAXParseException exception) throws SAXException {
+149                      xmlerror.set(exception);
+150                      passed.set(false);
+151                  }
+152                  
+153              });
+154              Document doc = reader.read(in);
+155              if (!passed.get()) {
+156                  // TODO: provide details (maybe via separate error object?)
+157                  throw xmlerror.get();
+158              }
+159              
+160              doc.normalize();
+161              
+162              Element root = doc.getRootElement();
+163              metadata.read(root.element(TAG_METADATA));
+164              
+165              if (graph instanceof Graph) {
+166                  readGraph(root.element(TAG_GRAPH));
+167              } else if (graph instanceof Digraph) {
+168                  readGraph(root.element(TAG_DIGRAPH));
+169              } else {
+170                  throw new IOException("unsupported graph type");
+171              }
+172          } catch (DocumentException | SAXException ex) {
+173              throw new IOException(ex);
+174          }
+175      }
+176  }
+
+ + + diff -r 903b46fc4214 -r c06ab07fd29d test/v2-regression/plain.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/v2-regression/plain.html Tue Aug 23 14:13:46 2016 +0200 @@ -0,0 +1,53 @@ + + + + c2html + + + + +
+1  </body>
+2  </html>
+3  <!c
+4  pblock_free(q);
+5  !>
+6  
+
+ + +