def to_puz(self, filename: str) -> None: """Outputs a .puz file from the Crossword object. Args: filename (str): The output path. """ puz_black, puz_empty = ".", "-" puz_obj = puz.Puzzle() puz_obj.height = self.num_rows puz_obj.width = self.num_cols char_array = np.array([cell.str for cell in self._grid.ravel()]) puz_obj.solution = ("".join(char_array).replace( EMPTY.str, puz_empty).replace(BLACK.str, puz_black)) fill_grid = char_array.copy() fill_grid[fill_grid != BLACK.str] = puz_empty fill_grid[fill_grid == BLACK.str] = puz_black puz_obj.fill = "".join(fill_grid) sorted_words = sorted(list(self.iterwords()), key=lambda w: (w.number, w.direction)) puz_obj.clues = [w.clue for w in sorted_words] puz_obj.cksum_global = puz_obj.global_cksum() puz_obj.cksum_hdr = puz_obj.header_cksum() puz_obj.cksum_magic = puz_obj.magic_cksum() puz_obj.save(filename)
def parse_xword(self, xword_data): xword_metadata = xword_data.get('copy', '') xword_data = xword_data.get('grid', '') date_string = xword_metadata.get('date-publish-analytics').split()[0] self.date = datetime.datetime.strptime(date_string, '%Y/%m/%d') fetched = {} for field in ['title', 'byline', 'publisher']: fetched[field] = html2text(xword_metadata.get(field, ''), bodywidth=0).strip() puzzle = puz.Puzzle() puzzle.title = fetched.get('title') puzzle.author = fetched.get('byline') puzzle.copyright = fetched.get('publisher') puzzle.width = int(xword_metadata.get('gridsize').get('cols')) puzzle.height = int(xword_metadata.get('gridsize').get('rows')) solution = '' fill = '' markup = b'' for row in xword_data: for cell in row: if not cell['Letter']: fill += '.' solution += '.' markup += b'\x00' else: fill += '-' solution += cell['Letter'] markup += (b'\x80' if (cell.get('style','') and cell['style']['shapebg'] == 'circle') \ else b'\x00') puzzle.fill = fill puzzle.solution = solution clue_list = xword_metadata['clues'][0]['clues'] + xword_metadata[ 'clues'][1]['clues'] sorted_clue_list = sorted(clue_list, key=lambda x: int(x['number'])) clues = [clue['clue'] for clue in sorted_clue_list] normalized_clues = [ html2text(unidecode(clue), bodywidth=0) for clue in clues ] puzzle.clues = normalized_clues has_markup = b'\x80' in markup if has_markup: puzzle.extensions[b'GEXT'] = markup puzzle._extensions_order.append(b'GEXT') puzzle.markup() return puzzle
def __init__(self, output=None): self.output = output if self.output and not self.output.endswith('.puz'): self.output = self.output + '.puz' self.puzfile = puz.Puzzle() self.outlet_prefix = None self.date = None
def test_save_empty_puzzle(self): ''' confirm an empty Puzzle() can be saved to a file ''' p = puz.Puzzle() with tempfile.NamedTemporaryFile(suffix='.puz') as tmp: p.save(tmp.name) p2 = puz.read(tmp.name) self.assertEqual(p.puzzletype, p2.puzzletype) self.assertEqual(p.version, p2.version) self.assertEqual(p.scrambled_cksum, p2.scrambled_cksum)
def get_amuse_puzzle(url, output): res = requests.get(url) rawc = next((line.strip() for line in res.text.splitlines() if 'window.rawc' in line), None) if not rawc: sys.exit("Crossword puzzle not found.") rawc = rawc.lstrip("window.rawc = '") rawc = rawc.rstrip("';") xword_data = json.loads(base64.b64decode(rawc)) p = puz.Puzzle() p.title = xword_data.get('title', '') p.author = xword_data.get('author', '') p.copyright = xword_data.get('copyright', '') p.width = xword_data.get('w') p.height = xword_data.get('h') solution = '' fill = '' box = xword_data['box'] for row_num in range(xword_data.get('h')): for column in box: cell = column[row_num] if cell == '\x00': solution += '.' fill += '.' else: solution += cell fill += '-' p.solution = solution p.fill = fill placed_words = xword_data['placedWords'] across_words = [word for word in placed_words if word['acrossNotDown']] down_words = [word for word in placed_words if not word['acrossNotDown']] weirdass_puz_clue_sorting = sorted( placed_words, key=lambda word: (word['y'], word['x'], not word['acrossNotDown'])) clues = [word['clue']['clue'] for word in weirdass_puz_clue_sorting] normalized_clues = [ html2text(unidecode(clue), bodywidth=0) for clue in clues ] p.clues.extend(normalized_clues) p.save(output) print("Puzzle downloaded and saved as {}.".format(output))
def crossword_to_puz(crossword): p = puz.Puzzle() p.preamble = b"" p.title = "PanLex Crossword" p.author = "PanLex" p.copyright = "n/a" p.width = crossword.cols p.height = crossword.rows p.solution = grid_to_solution(crossword.best_grid) p.fill = re.sub(r"[^.]", "-", p.solution) p.clues = [w[1] for w in sorted(crossword.best_wordlist, key=lambda x: x[2:])] return p
def parse_xword(self, xword_data): fetched = {} for field in ['Title', 'Author', 'Editor', 'Copryight']: fetched[field] = urllib.parse.unquote(xword_data.get(field, '')).strip() puzzle = puz.Puzzle() puzzle.title = fetched.get('Title', '') puzzle.author = ''.join( [fetched.get('Author', ''), ' / Ed. ', fetched.get('Editor', '')]) puzzle.copyright = fetched.get('Copyright', '') puzzle.width = int(xword_data.get('Width')) puzzle.height = int(xword_data.get('Height')) solution = xword_data.get('AllAnswer').replace('-', '.') puzzle.solution = solution fill = '' for letter in solution: if letter == '.': fill += '.' else: fill += '-' puzzle.fill = fill across_clues = xword_data['AcrossClue'].splitlines() down_clues = self.process_clues(xword_data['DownClue'].splitlines()) clues_list = across_clues + down_clues clues_list_stripped = [{ 'number': clue.split('|')[0], 'clue': clue.split('|')[1] } for clue in clues_list] clues_sorted = sorted(clues_list_stripped, key=lambda x: x['number']) clues = [clue['clue'] for clue in clues_sorted] puzzle.clues = clues return puzzle
def test_save_small_puzzle(self): ''' an example of creating a small 3x3 puzzle from scratch and writing to a file ''' p = puz.Puzzle() with tempfile.NamedTemporaryFile(suffix='.puz') as tmp: p.title = 'Test Puzzle' p.author = 'Alex' p.height = 3 p.width = 3 p.solution = 'A' * 9 p.clues = ['clue'] * 6 p.fill = '-' * 9 p.save(tmp.name) p2 = puz.read(tmp.name) self.assertEqual(p.title, p2.title) self.assertEqual(p.author, p2.author) self.assertEqual(p.solution, p2.solution) self.assertEqual(p.clues, p2.clues) self.assertEqual(p.fill, p2.fill)
def printBinary(ip): np = puz.Puzzle() np.author = ip['author'] np.title = ip['title'] np.copyright = ip['copyright'] np.width = ip['dimensions']['width'] np.height = ip['dimensions']['height'] # Converting a solution: The ipuz format is an array of array of chars. # The puz format is a single string, known to be width x height chars. # Just need to convert # to . as each of those is the empty cell marking. sstrings = [''.join(row) for row in ip['solution']] sstring = ''.join(sstrings) sstring = sstring.replace('#', '.') np.solution = ''.join(sstring) np.fill = re.sub(r"[^.]", "-", np.solution) # origin and publisher fields ignored in ipuzzle. What about kind? # Ignoring 'puzzle' field, which contains a sophisticated grid format. # Fortunately ipuz specifies clue numbers, which allows us to order # in PUZ's unclear clue ordering. a = [(n, latin1ify(clue)) for n, clue in ip['clues']['Across']] d = [(n + 0.5, latin1ify(clue)) for n, clue in ip['clues']['Down']] np.clues = [clue for _, clue in sorted(a + d)] np.markup().markup = [0] * np.width * np.height for y, cols in enumerate(ip['puzzle']): for x, cell in enumerate(cols): if not isinstance(cell, dict): continue shapebg = cell.get('style', {}).get('shapebg') if shapebg == 'circle': np.markup().markup[y * np.width + x] = puz.GridMarkup.Circled # np.preamble = ip['intro'] np.save(output_file)
def to_puz(self, filename: str): """Ouputs a .puz file from the Crossword object. Args: filename (str): The output path. """ puz_obj = puz.Puzzle() puz_obj.height = self.num_rows puz_obj.width = self.num_cols puz_obj.solution = ("".join(self._grid.ravel()).replace( black, ".").replace(empty, "-")) fill_grid = self._grid.copy() fill_grid[fill_grid != black] = "-" fill_grid[fill_grid == black] = "." puz_obj.fill = "".join(fill_grid.ravel()) sorted_words = sorted( list(self.iterwords()), key=lambda w: w.number + (0.5 if w.direction == down else 0), ) puz_obj.clues = [w.clue for w in sorted_words] puz_obj.cksum_global = puz_obj.global_cksum() puz_obj.cksum_hdr = puz_obj.header_cksum() puz_obj.cksum_magic = puz_obj.magic_cksum() puz_obj.save(filename)
metavar='p', type=str, help='Path to the output .puz file') parser.add_argument('--cluesfile', metavar='c', type=str, help="Path to the clues file") parser.add_argument('--author', metavar='a', type=str, help="Author") parser.add_argument('--title', metavar='t', type=str, help="Title") parser.add_argument('--copyright', metavar='t', type=str, help="Title") ns = parser.parse_args() (rows, cols, solution) = ReadQXW(ns.qxwfile) puzzle = puz.Puzzle() puzzle.solution = "".join(solution).upper() puzzle.height = rows puzzle.width = cols puzzle.fill = "".join([('.' if c == '.' else '-') for c in solution]) clues = [s.strip() for s in file(ns.cluesfile).readlines()] puzzle.clues = OrderClues(clues, puzzle.fill, rows, cols) puzzle.author = ns.author puzzle.title = ns.title # Another tool, decode_crossword.perl, explicitly looks for the copyright symbol 0xA9. So, include it. puzzle.copyright = u'' + unichr(0xA9) + (ns.copyright if ns.copyright else "") puzzle.notes = "" print puzzle.__dict__
def parse_xword(self, xword_data): puzzle = puz.Puzzle() puzzle.title = xword_data.get('title', '').strip() puzzle.author = xword_data.get('author', '').strip() puzzle.copyright = xword_data.get('copyright', '').strip() puzzle.width = xword_data.get('w') puzzle.height = xword_data.get('h') markup_data = xword_data.get('cellInfos', '') circled = [(square['x'], square['y']) for square in markup_data if square['isCircled']] solution = '' fill = '' markup = b'' rebus_board = [] rebus_index = 0 rebus_table = '' box = xword_data['box'] for row_num in range(xword_data.get('h')): for col_num, column in enumerate(box): cell = column[row_num] if cell == '\x00': solution += '.' fill += '.' markup += b'\x00' rebus_board.append(0) elif len(cell) == 1: solution += cell fill += '-' markup += b'\x80' if (col_num, row_num) in circled else b'\x00' rebus_board.append(0) else: solution += cell[0] fill += '-' rebus_board.append(rebus_index + 1) rebus_table += '{:2d}:{};'.format(rebus_index, cell) rebus_index += 1 puzzle.solution = solution puzzle.fill = fill placed_words = xword_data['placedWords'] across_words = [word for word in placed_words if word['acrossNotDown']] down_words = [ word for word in placed_words if not word['acrossNotDown'] ] weirdass_puz_clue_sorting = sorted( placed_words, key=lambda word: (word['y'], word['x'], not word['acrossNotDown'])) clues = [word['clue']['clue'] for word in weirdass_puz_clue_sorting] normalized_clues = [ html2text(unidecode(clue), bodywidth=0) for clue in clues ] puzzle.clues.extend(normalized_clues) has_markup = b'\x80' in markup has_rebus = any(rebus_board) if has_markup: puzzle.extensions[b'GEXT'] = markup puzzle._extensions_order.append(b'GEXT') puzzle.markup() if has_rebus: puzzle.extensions[b'GRBS'] = bytes(rebus_board) puzzle.extensions[b'RTBL'] = rebus_table.encode(puz.ENCODING) puzzle._extensions_order.extend([b'GRBS', b'RTBL']) puzzle.rebus() return puzzle
def test_junk_at_end_of_puzzle(self): with open('testfiles/washpost.puz', 'rb') as fp: data = fp.read() + b'\r\n\r\n' p = puz.Puzzle() p.load(data) self.assertEqual(p.postscript, b'\r\n\r\n')
def test_empty_puzzle(self): p = puz.Puzzle() self.assertRaises(puz.PuzzleFormatError, p.load, b'')
def parse_xword(self, xword_data): puzzle = puz.Puzzle() metadata = xword_data.get('puzzle_meta') puzzle.author = metadata.get('author').strip() puzzle.copyright = metadata.get('copyright').strip() puzzle.height = metadata.get('height') puzzle.width = metadata.get('width') if metadata.get('notes'): puzzle.notes = metadata.get('notes')[0]['txt'].strip() date_string = metadata.get('printDate') self.date = datetime.datetime.strptime(date_string, '%Y-%m-%d') puzzle.title = metadata.get('title') or self.date.strftime( '%A, %B %d, %Y') puzzle_data = xword_data['puzzle_data'] solution = '' fill = '' markup = b'' rebus_board = [] rebus_index = 0 rebus_table = '' for idx, square in enumerate(puzzle_data['answers']): if not square: solution += '.' fill += '.' rebus_board.append(0) elif len(square) == 1: solution += square fill += '-' rebus_board.append(0) else: solution += square[0][0] fill += '-' rebus_board.append(rebus_index + 1) rebus_table += '{:2d}:{};'.format(rebus_index, square[0]) rebus_index += 1 markup += (b'\x80' if puzzle_data['layout'][idx] == 3 else b'\x00') puzzle.solution = solution puzzle.fill = fill clue_list = puzzle_data['clues']['A'] + puzzle_data['clues']['D'] clue_list.sort(key=lambda c: c['clueNum']) puzzle.clues = [unidecode(c['value']).strip() for c in clue_list] if b'\x80' in markup: puzzle.extensions[b'GEXT'] = markup puzzle._extensions_order.append(b'GEXT') puzzle.markup() if any(rebus_board): puzzle.extensions[b'GRBS'] = bytes(rebus_board) puzzle.extensions[b'RTBL'] = rebus_table.encode(puz.ENCODING) puzzle._extensions_order.extend([b'GRBS', b'RTBL']) puzzle.rebus() return puzzle