1
/* ipuz-puzzle.c
2
 *
3
 * Copyright 2022 Jonathan Blandford <jrb@gnome.org>
4
 *
5
 * This library is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Lesser General Public
7
 * License as published by the Free Software Foundation; either
8
 * version 2.1 of the License, or (at your option) any later version.
9
 *
10
 * This library is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 * Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public
16
 * License along with this library; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18
 *
19
 * SPDX-License-Identifier: (LGPL-2.1-or-later OR MIT)
20
 */
21

            
22

            
23

            
24
#include "libipuz.h"
25
#include "libipuz-enums.h"
26
#include <json-glib/json-glib.h>
27
#include "libipuz-config.h"
28
#include <glib/gi18n-lib.h>
29
#include "ipuz-magic.h"
30
#include "ipuz-misc.h"
31
#include "ipuz-puzzle-info-private.h"
32

            
33
// FIXME: Temporary until this is done in rust
34
6
G_DEFINE_BOXED_TYPE(IPuzCharset, ipuz_charset, ipuz_charset_ref, ipuz_charset_unref);
35

            
36

            
37
enum
38
{
39
  PROP_0,
40
  PROP_PUZZLE_KIND,
41
  PROP_VERSION,
42
  PROP_COPYRIGHT,
43
  PROP_PUBLISHER,
44
  PROP_PUBLICATION,
45
  PROP_URL,
46
  PROP_UNIQUEID,
47
  PROP_TITLE,
48
  PROP_INTRO,
49
  PROP_EXPLANATION,
50
  PROP_ANNOTATION,
51
  PROP_AUTHOR,
52
  PROP_EDITOR,
53
  PROP_DATE,
54
  PROP_NOTES,
55
  PROP_DIFFICULTY,
56
  PROP_CHARSET,
57
  PROP_CHARSET_STR,
58
  PROP_ORIGIN,
59
  PROP_BLOCK,
60
  PROP_EMPTY,
61
  PROP_STYLES,
62
  PROP_LICENSE,
63
  PROP_LOCALE,
64
  N_PROPS
65
};
66
static GParamSpec *obj_props[N_PROPS] = { NULL, };
67

            
68
struct _IPuzPuzzlePrivate
69
{
70
  gchar *version;
71
  gchar *copyright;
72
  gchar *publisher;
73
  gchar *publication;
74
  gchar *url;
75
  gchar *uniqueid;
76
  gchar *title;
77
  gchar *intro;
78
  gchar *explanation;
79
  gchar *annotation;
80
  gchar *author;
81
  gchar *editor;
82
  gchar *date;
83
  gchar *notes;
84
  gchar *difficulty;
85
  gchar *origin;
86
  gchar *block;
87
  gchar *empty;
88
  GHashTable *styles;
89

            
90
  /* Charset */
91
  IPuzCharset *charset;
92
  gchar *charset_str;
93

            
94

            
95
  /* Extenstions */
96
  gchar *license;
97
  gchar *locale;
98

            
99
  /* FIXME(checksum): add support for this */
100
  gchar *checksum_salt;
101
  gchar **checksums;
102
};
103

            
104

            
105
typedef struct _IPuzPuzzlePrivate IPuzPuzzlePrivate;
106

            
107

            
108
static void                ipuz_puzzle_init                (IPuzPuzzle      *self);
109
static void                ipuz_puzzle_class_init          (IPuzPuzzleClass *klass);
110
static void                ipuz_puzzle_dispose             (GObject         *object);
111
static void                ipuz_puzzle_finalize            (GObject         *object);
112
static void                ipuz_puzzle_set_property        (GObject         *object,
113
                                                            guint            prop_id,
114
                                                            const GValue    *value,
115
                                                            GParamSpec      *pspec);
116
static void                ipuz_puzzle_get_property        (GObject         *object,
117
                                                            guint            prop_id,
118
                                                            GValue          *value,
119
                                                            GParamSpec      *pspec);
120
static void                ipuz_puzzle_real_load_node      (IPuzPuzzle      *puzzle,
121
                                                            const char      *member_name,
122
                                                            JsonNode        *node);
123
static void                ipuz_puzzle_real_post_load_node (IPuzPuzzle      *puzzle,
124
                                                            const char      *member_name,
125
                                                            JsonNode        *node);
126
static void                ipuz_puzzle_real_fixup          (IPuzPuzzle      *puzzle);
127
static void                ipuz_puzzle_real_validate       (IPuzPuzzle      *puzzle);
128
static void                ipuz_puzzle_real_build          (IPuzPuzzle      *puzzle,
129
                                                            JsonBuilder     *builder);
130
static IPuzPuzzleFlags     ipuz_puzzle_real_get_flags      (IPuzPuzzle      *puzzle);
131
static void                ipuz_puzzle_real_calculate_info (IPuzPuzzle      *puzzle,
132
                                                            IPuzPuzzleInfo  *info);
133
static void                ipuz_puzzle_real_clone          (IPuzPuzzle      *src,
134
                                                            IPuzPuzzle      *dest);
135
static const char * const *ipuz_puzzle_real_get_kind_str   (IPuzPuzzle      *puzzle);
136
static void                ipuz_puzzle_real_set_style      (IPuzPuzzle      *puzzle,
137
                                                            const char      *style_name,
138
                                                            IPuzStyle       *style);
139
static gboolean            ipuz_puzzle_real_equal          (IPuzPuzzle      *puzzle_a,
140
                                                            IPuzPuzzle      *puzzle_b);
141
static void                ensure_charset                  (IPuzPuzzle      *self);
142

            
143

            
144
6
G_DEFINE_TYPE_WITH_CODE (IPuzPuzzle, ipuz_puzzle, G_TYPE_OBJECT, G_ADD_PRIVATE (IPuzPuzzle));
145

            
146

            
147
/*
148
 * Class Methods
149
 */
150

            
151
static void
152
39
ipuz_puzzle_init (IPuzPuzzle *puzzle)
153
{
154
  IPuzPuzzlePrivate *priv;
155

            
156
39
  priv = ipuz_puzzle_get_instance_private (puzzle);
157

            
158
39
  priv->version = g_strdup (_IPUZ_VERSION_2);
159
39
  priv->block = g_strdup (_IPUZ_DEFAULT_BLOCK);
160
39
  priv->empty = g_strdup (_IPUZ_DEFAULT_EMPTY);
161

            
162
39
  ensure_charset (puzzle);
163
39
}
164

            
165
static void
166
6
ipuz_puzzle_class_init (IPuzPuzzleClass *klass)
167
{
168
6
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
169

            
170
6
  object_class->finalize = ipuz_puzzle_finalize;
171
6
  object_class->dispose = ipuz_puzzle_dispose;
172
6
  object_class->set_property = ipuz_puzzle_set_property;
173
6
  object_class->get_property = ipuz_puzzle_get_property;
174
6
  klass->load_node = ipuz_puzzle_real_load_node;
175
6
  klass->post_load_node = ipuz_puzzle_real_post_load_node;
176
6
  klass->fixup = ipuz_puzzle_real_fixup;
177
6
  klass->validate = ipuz_puzzle_real_validate;
178
6
  klass->build = ipuz_puzzle_real_build;
179
6
  klass->get_flags = ipuz_puzzle_real_get_flags;
180
6
  klass->calculate_info = ipuz_puzzle_real_calculate_info;
181
6
  klass->clone = ipuz_puzzle_real_clone;
182
6
  klass->get_kind_str = ipuz_puzzle_real_get_kind_str;
183
6
  klass->set_style = ipuz_puzzle_real_set_style;
184
6
  klass->equal = ipuz_puzzle_real_equal;
185

            
186
6
  obj_props[PROP_PUZZLE_KIND] = g_param_spec_enum ("puzzle-kind",
187
						   "Puzzle Kind",
188
						   "The type of puzzle",
189
						   I_TYPE_PUZ_PUZZLE_KIND,
190
						   IPUZ_PUZZLE_UNKNOWN,
191
						   G_PARAM_READABLE);
192
6
  obj_props[PROP_VERSION] = g_param_spec_string ("version",
193
						 "Version",
194
						 "Version of ipuz for this puzzle",
195
						 _IPUZ_VERSION_2,
196
                                                 G_PARAM_READWRITE);
197
6
  obj_props[PROP_COPYRIGHT] = g_param_spec_string ("copyright",
198
						   "Copyright",
199
						   "Copyright information",
200
						   NULL,
201
						   G_PARAM_READWRITE);
202
6
  obj_props[PROP_PUBLISHER] = g_param_spec_string ("publisher",
203
						   "Publisher",
204
						   "Name and/or reference for a publisher",
205
						   NULL,
206
						   G_PARAM_READWRITE);
207
6
  obj_props[PROP_PUBLICATION] = g_param_spec_string ("publication",
208
						     "Publication",
209
						     "Bibliographic reference for a published puzzle",
210
						     NULL,
211
						     G_PARAM_READWRITE);
212
6
  obj_props[PROP_URL] = g_param_spec_string ("url",
213
					     "URL",
214
					     "Permanent URL for the puzzle",
215
					     NULL,
216
					     G_PARAM_READWRITE);
217
6
  obj_props[PROP_UNIQUEID] = g_param_spec_string ("uniqueid",
218
						  "Unique ID",
219
						  "Globally unique identifier for the puzzle",
220
						  NULL,
221
						  G_PARAM_READWRITE);
222
6
  obj_props[PROP_TITLE] = g_param_spec_string ("title",
223
					       "Title",
224
					       "Title of puzzle",
225
					       NULL,
226
					       G_PARAM_READWRITE);
227
6
  obj_props[PROP_INTRO] = g_param_spec_string ("intro",
228
					       "Intro",
229
					       "Text displayed above puzzle",
230
					       NULL,
231
					       G_PARAM_READWRITE);
232
6
  obj_props[PROP_EXPLANATION] = g_param_spec_string ("explanation",
233
						     "Explanation",
234
						     "Text displayed after successful solve",
235
						     NULL,
236
						     G_PARAM_READWRITE);
237
6
  obj_props[PROP_ANNOTATION] = g_param_spec_string ("annotation",
238
						    "Annotation",
239
						    "Non-displayed annotation",
240
						    NULL,
241
						    G_PARAM_READWRITE);
242
6
  obj_props[PROP_AUTHOR] = g_param_spec_string ("author",
243
						"Author",
244
						"Author of puzzle",
245
						NULL,
246
						G_PARAM_READWRITE);
247
6
  obj_props[PROP_EDITOR] = g_param_spec_string ("editor",
248
						"Editor",
249
						"Editor of puzzle",
250
						NULL,
251
						G_PARAM_READWRITE);
252
6
  obj_props[PROP_DATE] = g_param_spec_string ("date",
253
					      "Date",
254
					      "Date of puzzle or publication date",
255
					      NULL,
256
					      G_PARAM_READWRITE);
257
6
  obj_props[PROP_NOTES] = g_param_spec_string ("notes",
258
					       "Notes",
259
					       "Notes about the puzzle",
260
					       NULL,
261
					       G_PARAM_READWRITE);
262
6
  obj_props[PROP_DIFFICULTY] = g_param_spec_string ("difficulty",
263
						    "Difficulty",
264
						    "Informational only, there is no standard for difficulty",
265
						    NULL,
266
						    G_PARAM_READWRITE);
267
6
  obj_props[PROP_CHARSET] = g_param_spec_boxed ("charset",
268
                                                "Charset",
269
                                                "Characters that can be entered in the puzzle",
270
						 IPUZ_TYPE_CHARSET,
271
						 G_PARAM_READWRITE);
272
6
  obj_props[PROP_CHARSET_STR] = g_param_spec_string ("charset-str",
273
                                                     "Charset String",
274
                                                     "Characters that can be entered in the puzzle, in string form",
275
                                                     NULL,
276
                                                     G_PARAM_READWRITE);
277
6
  obj_props[PROP_ORIGIN] = g_param_spec_string ("origin",
278
						"Origin",
279
						"Program-specific information from program that wrote this file",
280
						NULL,
281
						G_PARAM_READWRITE);
282
6
  obj_props[PROP_BLOCK] = g_param_spec_string ("block",
283
					       "Block",
284
					       "Text value which represents a block",
285
					       _IPUZ_DEFAULT_BLOCK,
286
					       G_PARAM_READWRITE);
287
6
  obj_props[PROP_EMPTY] = g_param_spec_string ("empty",
288
					       "Empty",
289
					       "Value which represents an empty cell",
290
					       _IPUZ_DEFAULT_EMPTY,
291
					       G_PARAM_READWRITE);
292
6
  obj_props[PROP_STYLES] = g_param_spec_boxed ("styles",
293
                                               "Styles",
294
                                               "Named styles for the puzzle",
295
                                               G_TYPE_HASH_TABLE,
296
                                               G_PARAM_READWRITE);
297
6
  obj_props[PROP_LICENSE] = g_param_spec_string ("license",
298
                                                 "License",
299
                                                 "License of the puzzle",
300
                                                 NULL,
301
                                                 G_PARAM_READWRITE);
302
6
  obj_props[PROP_LOCALE] = g_param_spec_string ("locale",
303
                                                "Locale",
304
                                                "Locale of the puzzle",
305
                                                _IPUZ_DEFAULT_LOCALE,
306
                                                G_PARAM_READWRITE);
307
6
  g_object_class_install_properties (object_class, N_PROPS, obj_props);
308

            
309
  /* I'm not sure where else to put this. */
310
6
  bindtextdomain (GETTEXT_PACKAGE, LIBIPUZ_LOCALEDIR);
311
6
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
312

            
313
6
}
314

            
315

            
316
/* drop all refs */
317
static void
318
39
ipuz_puzzle_dispose (GObject *object)
319
{
320
  IPuzPuzzlePrivate *priv;
321

            
322
39
  priv = ipuz_puzzle_get_instance_private (IPUZ_PUZZLE (object));
323

            
324
39
  g_clear_pointer (&priv->styles, g_hash_table_unref);
325
39
  G_OBJECT_CLASS (ipuz_puzzle_parent_class)->dispose (object);
326
39
}
327

            
328
/* free all memory */
329
static void
330
39
ipuz_puzzle_finalize (GObject *object)
331
{
332
  IPuzPuzzlePrivate *priv;
333

            
334
39
  g_return_if_fail (object != NULL);
335

            
336
39
  priv = ipuz_puzzle_get_instance_private (IPUZ_PUZZLE (object));
337

            
338
39
  g_free (priv->version);
339
39
  g_free (priv->copyright);
340
39
  g_free (priv->publisher);
341
39
  g_free (priv->publication);
342
39
  g_free (priv->url);
343
39
  g_free (priv->uniqueid);
344
39
  g_free (priv->title);
345
39
  g_free (priv->intro);
346
39
  g_free (priv->explanation);
347
39
  g_free (priv->annotation);
348
39
  g_free (priv->author);
349
39
  g_free (priv->editor);
350
39
  g_free (priv->date);
351
39
  g_free (priv->notes);
352
39
  g_free (priv->difficulty);
353
39
  g_clear_pointer (&priv->charset, ipuz_charset_unref);
354
39
  g_free (priv->charset_str);
355
39
  g_free (priv->origin);
356
39
  g_free (priv->block);
357
39
  g_free (priv->empty);
358
39
  g_free (priv->license);
359
39
  g_free (priv->locale);
360
39
  g_free (priv->checksum_salt);
361
39
  g_strfreev (priv->checksums);
362

            
363
39
  if (priv->styles)
364
    {
365
      g_hash_table_unref (priv->styles);
366
    }
367

            
368
39
  G_OBJECT_CLASS (ipuz_puzzle_parent_class)->finalize (object);
369
}
370

            
371

            
372
static void
373
202
ipuz_puzzle_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
374
{
375
  IPuzPuzzle *self;
376
  IPuzPuzzlePrivate *priv;
377

            
378
202
  g_return_if_fail (object != NULL);
379

            
380
202
  self = IPUZ_PUZZLE (object);
381
202
  priv = ipuz_puzzle_get_instance_private (self);
382

            
383
202
  switch (prop_id)
384
    {
385
64
    case PROP_VERSION:
386
64
      g_free (priv->version);
387
64
      priv->version = g_value_dup_string (value);
388
64
      break;
389
    case PROP_COPYRIGHT:
390
      g_free (priv->copyright);
391
      priv->copyright = g_value_dup_string (value);
392
      break;
393
15
    case PROP_PUBLISHER:
394
15
      g_free (priv->publisher);
395
15
      priv->publisher = ipuz_html_to_markup (g_value_get_string (value));
396
15
      break;
397
    case PROP_PUBLICATION:
398
      g_free (priv->publication);
399
      priv->publication = ipuz_html_to_markup (g_value_get_string (value));
400
      break;
401
    case PROP_URL:
402
      g_free (priv->url);
403
      priv->url = g_value_dup_string (value);
404
      break;
405
    case PROP_UNIQUEID:
406
      g_free (priv->uniqueid);
407
      priv->uniqueid = g_value_dup_string (value);
408
      break;
409
26
    case PROP_TITLE:
410
26
      g_free (priv->title);
411
26
      priv->title = ipuz_html_to_markup (g_value_get_string (value));
412
26
      break;
413
8
    case PROP_INTRO:
414
8
      g_free (priv->intro);
415
8
      priv->intro = ipuz_html_to_markup (g_value_get_string (value));
416
8
      break;
417
    case PROP_EXPLANATION:
418
      g_free (priv->explanation);
419
      priv->explanation = ipuz_html_to_markup (g_value_get_string (value));
420
      break;
421
    case PROP_ANNOTATION:
422
      g_free (priv->annotation);
423
      priv->annotation = g_value_dup_string (value);
424
      break;
425
26
    case PROP_AUTHOR:
426
26
      g_free (priv->author);
427
26
      priv->author = ipuz_html_to_markup (g_value_get_string (value));
428
26
      break;
429
    case PROP_EDITOR:
430
      g_free (priv->editor);
431
      priv->editor = ipuz_html_to_markup (g_value_get_string (value));
432
      break;
433
24
    case PROP_DATE:
434
24
      g_free (priv->date);
435
24
      priv->date = g_value_dup_string (value);
436
24
      break;
437
12
    case PROP_NOTES:
438
12
      g_free (priv->notes);
439
12
      priv->notes = ipuz_html_to_markup (g_value_get_string (value));
440
12
      break;
441
    case PROP_DIFFICULTY:
442
      g_free (priv->difficulty);
443
      priv->difficulty = ipuz_html_to_markup (g_value_get_string (value));
444
      break;
445
    case PROP_CHARSET:
446
      ipuz_puzzle_set_charset (self, g_value_get_boxed (value));
447
      break;
448
2
    case PROP_CHARSET_STR:
449
2
      ipuz_puzzle_set_charset_str (self, g_value_get_string (value));
450
2
      break;
451
8
    case PROP_ORIGIN:
452
8
      g_free (priv->origin);
453
8
      priv->origin = g_value_dup_string (value);
454
8
      break;
455
8
    case PROP_BLOCK:
456
8
      g_free (priv->block);
457
8
      priv->block = g_value_dup_string (value);
458
8
      if (priv->block == NULL)
459
        priv->block = g_strdup (_IPUZ_DEFAULT_BLOCK);
460
8
      break;
461
5
    case PROP_EMPTY:
462
5
      g_free (priv->empty);
463
5
      priv->empty = g_value_dup_string(value);
464
5
      if (priv->empty == NULL)
465
        priv->empty = g_strdup (_IPUZ_DEFAULT_EMPTY);
466
5
      break;
467
    case PROP_STYLES:
468
      if (priv->styles)
469
        g_hash_table_unref (priv->styles);
470
      priv->styles = g_value_dup_boxed (value);
471
      break;
472
4
    case PROP_LICENSE:
473
4
      g_free (priv->license);
474
4
      priv->license = g_value_dup_string (value);
475
4
      break;
476
    case PROP_LOCALE:
477
      g_free (priv->locale);
478
      priv->locale = g_value_dup_string(value);
479
      if (priv->locale == NULL)
480
        priv->locale = g_strdup (_IPUZ_DEFAULT_LOCALE);
481
      break;
482
    default:
483
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
484
      break;
485
    }
486
}
487

            
488
static void
489
276
ipuz_puzzle_get_property (GObject    *object,
490
                          guint       prop_id,
491
                          GValue     *value,
492
                          GParamSpec *pspec)
493
{
494
  IPuzPuzzle *self;
495
  IPuzPuzzlePrivate *priv;
496

            
497
276
  g_return_if_fail (object != NULL);
498

            
499
276
  self = IPUZ_PUZZLE (object);
500
276
  priv = ipuz_puzzle_get_instance_private (self);
501

            
502
276
  switch (prop_id)
503
    {
504
    case PROP_PUZZLE_KIND:
505
      g_value_set_enum (value, ipuz_puzzle_get_puzzle_kind (self));
506
      break;
507
2
    case PROP_VERSION:
508
2
      g_value_set_string (value, priv->version);
509
2
      break;
510
6
    case PROP_COPYRIGHT:
511
6
      g_value_set_string (value, priv->copyright);
512
6
      break;
513
6
    case PROP_PUBLISHER:
514
6
      g_value_set_string (value, priv->publisher);
515
6
      break;
516
6
    case PROP_PUBLICATION:
517
6
      g_value_set_string (value, priv->publication);
518
6
      break;
519
6
    case PROP_URL:
520
6
      g_value_set_string (value, priv->url);
521
6
      break;
522
6
    case PROP_UNIQUEID:
523
6
      g_value_set_string (value, priv->uniqueid);
524
6
      break;
525
6
    case PROP_TITLE:
526
6
      g_value_set_string (value, priv->title);
527
6
      break;
528
6
    case PROP_INTRO:
529
6
      g_value_set_string (value, priv->intro);
530
6
      break;
531
6
    case PROP_EXPLANATION:
532
6
      g_value_set_string (value, priv->explanation);
533
6
      break;
534
6
    case PROP_ANNOTATION:
535
6
      g_value_set_string (value, priv->annotation);
536
6
      break;
537
6
    case PROP_AUTHOR:
538
6
      g_value_set_string (value, priv->author);
539
6
      break;
540
6
    case PROP_EDITOR:
541
6
      g_value_set_string (value, priv->editor);
542
6
      break;
543
6
    case PROP_DATE:
544
6
      g_value_set_string (value, priv->date);
545
6
      break;
546
6
    case PROP_NOTES:
547
6
      g_value_set_string (value, priv->notes);
548
6
      break;
549
6
    case PROP_DIFFICULTY:
550
6
      g_value_set_string (value, priv->difficulty);
551
6
      break;
552
    case PROP_CHARSET:
553
      g_value_set_boxed (value, ipuz_puzzle_get_charset (self));
554
      break;
555
36
    case PROP_CHARSET_STR:
556
36
      g_value_set_string (value, ipuz_puzzle_get_charset_str (self));
557
36
      break;
558
6
    case PROP_ORIGIN:
559
6
      g_value_set_string (value, priv->origin);
560
6
      break;
561
70
    case PROP_BLOCK:
562
70
      if (priv->block)
563
70
        g_value_set_string (value, priv->block);
564
      else
565
        g_value_set_string (value, _IPUZ_DEFAULT_BLOCK);
566
70
      break;
567
40
    case PROP_EMPTY:
568
40
      if (priv->empty)
569
40
        g_value_set_string (value, priv->empty);
570
      else
571
        g_value_set_string (value, _IPUZ_DEFAULT_EMPTY);
572
40
      break;
573
34
    case PROP_STYLES:
574
34
      g_value_set_boxed (value, priv->styles);
575
34
      break;
576
2
    case PROP_LICENSE:
577
2
      g_value_set_string (value, priv->license);
578
2
      break;
579
2
    case PROP_LOCALE:
580
2
      g_value_set_string (value, priv->locale);
581
2
      break;
582
    default:
583
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
584
      break;
585
    }
586
}
587

            
588
static GHashTable *
589
4
new_styles_hash_table (void)
590
{
591
4
  return g_hash_table_new_full (g_str_hash, g_str_equal,
592
                                (GDestroyNotify) g_free,
593
                                (GDestroyNotify) ipuz_style_unref);
594
}
595

            
596
static void
597
4
ipuz_puzzle_real_load_styles (IPuzPuzzle *puzzle,
598
                              JsonNode   *node)
599
{
600
  IPuzPuzzlePrivate *priv;
601
4
  priv = ipuz_puzzle_get_instance_private (puzzle);
602
4
  JsonObjectIter iter = {0, };
603
4
  const gchar *member_name = NULL;
604
  JsonNode *member_node;
605

            
606
4
  if (!JSON_NODE_HOLDS_OBJECT (node))
607
    return;
608

            
609
4
  if (priv->styles == NULL)
610
4
    priv->styles = new_styles_hash_table ();
611

            
612
4
  json_object_iter_init (&iter, json_node_get_object (node));
613
23
  while (json_object_iter_next (&iter, &member_name, &member_node))
614
    {
615
15
      IPuzStyle *style = ipuz_style_new_from_json (member_node);
616
15
      ipuz_style_set_style_name (style, member_name);
617
15
      if (style != NULL)
618
15
        g_hash_table_insert (priv->styles, g_strdup (member_name), style);
619
    }
620
}
621

            
622
static void
623
296
ipuz_puzzle_real_load_node (IPuzPuzzle *puzzle,
624
                            const char *member_name,
625
                            JsonNode   *node)
626
{
627
  GObjectClass *object_class;
628
  GParamSpec *pspec;
629
296
  GValue value = G_VALUE_INIT;
630

            
631
296
  object_class = G_OBJECT_GET_CLASS (puzzle);
632

            
633
  /* styles isn't a simple property and needs manually loading */
634
296
  if (!g_strcmp0 (member_name, "styles"))
635
    {
636
4
      ipuz_puzzle_real_load_styles (puzzle, node);
637
296
      return;
638
    }
639

            
640
  /* Handle extensions separately */
641
580
  if (!g_strcmp0 (member_name, _IPUZ_X_LICENSE_TAG) ||
642
288
      !g_strcmp0 (member_name, _IPUZ_X_GNOME_LICENSE_TAG))
643
    {
644
4
      json_node_get_value (node, &value);
645
4
      g_object_set_property (G_OBJECT (puzzle), "license", &value);
646
4
      g_value_unset (&value);
647
4
      return;
648
    }
649

            
650
576
  if (!g_strcmp0 (member_name, _IPUZ_X_LOCALE_TAG) ||
651
288
      !g_strcmp0 (member_name, _IPUZ_X_GNOME_LOCALE_TAG))
652
    {
653
      json_node_get_value (node, &value);
654
      g_object_set_property (G_OBJECT (puzzle), "locale", &value);
655
      g_value_unset (&value);
656
      return;
657
    }
658

            
659
  /* Handle charsets a little differently. The property "charset"
660
   * isn't a string, but is "charset-str" instead */
661
288
  if (!g_strcmp0 (member_name, "charset"))
662
    {
663
2
      json_node_get_value (node, &value);
664
2
      ipuz_puzzle_set_charset_str (puzzle, g_value_get_string (&value));
665
2
      g_value_unset (&value);
666
2
      return;
667
    }
668

            
669
  /* handle a standard property */
670
286
  pspec = g_object_class_find_property (object_class, member_name);
671
286
  if (pspec == NULL)
672
94
    return;
673

            
674
  /* We don't do as comprehensive a job as the serializable interface, just
675
   * automatically handle strings and booleans. Everything else needs a custom
676
   * parser.
677
   */
678

            
679
192
  switch (G_PARAM_SPEC_VALUE_TYPE (pspec))
680
    {
681
166
    case G_TYPE_STRING:
682
166
      if (JSON_NODE_HOLDS_VALUE (node))
683
        {
684
166
          json_node_get_value (node, &value);
685
166
          g_object_set_property (G_OBJECT (puzzle), pspec->name, &value);
686
166
          g_value_unset (&value);
687
        }
688
166
      return;
689
26
    case G_TYPE_BOOLEAN:
690
26
      if (JSON_NODE_HOLDS_VALUE (node))
691
        {
692
26
          g_value_init (&value, G_TYPE_BOOLEAN);
693
26
          g_value_set_boolean (&value, json_node_get_boolean (node));
694
26
          g_object_set_property (G_OBJECT (puzzle), pspec->name, &value);
695
        }
696
26
      return;
697
    default:
698
      g_warning ("unable to convert %s", pspec->name);
699
      break;
700
    }
701
}
702

            
703
static void
704
ipuz_puzzle_real_post_load_node (IPuzPuzzle *puzzle,
705
                                 const char *member_name,
706
                                 JsonNode   *node)
707
{
708
  /* Empty function. Guard against accidentally being called by a subtype. */
709
}
710

            
711

            
712
static void
713
32
ipuz_puzzle_real_fixup (IPuzPuzzle *puzzle)
714
{
715
  /* Empty function. Guard against accidentally being called by a subtype. */
716
32
}
717

            
718
static void
719
32
ipuz_puzzle_real_validate (IPuzPuzzle *puzzle)
720
{
721
  /* FIXME: Implement someday */
722
32
}
723

            
724
static void
725
2
build_kind (IPuzPuzzle  *puzzle,
726
            JsonBuilder *builder)
727
{
728
  IPuzPuzzleClass *klass;
729
2
  const gchar *const *kind_str = NULL;
730

            
731
2
  klass = IPUZ_PUZZLE_GET_CLASS (puzzle);
732
2
  kind_str = klass->get_kind_str (puzzle);
733

            
734
2
  json_builder_set_member_name (builder, "kind");
735
2
  json_builder_begin_array (builder);
736

            
737
2
  if (kind_str == NULL)
738
    {
739
      json_builder_add_string_value (builder, "http://ipuz.org/crossword#1");
740
    }
741
  else
742
    {
743
2
      guint i = 0;
744

            
745
4
      while (kind_str[i] != NULL)
746
        {
747
2
          json_builder_add_string_value (builder, kind_str[i]);
748
2
          i++;
749
        }
750
    }
751
2
  json_builder_end_array (builder);
752
2
}
753

            
754
static void
755
42
build_string_param (IPuzPuzzle  *puzzle,
756
                    GParamSpec  *pspec,
757
                    JsonBuilder *builder)
758
{
759
  const gchar *name;
760
42
  GValue value = G_VALUE_INIT;
761

            
762
42
  name = g_param_spec_get_name (pspec);
763

            
764
42
  g_value_init (&value, G_TYPE_STRING);
765

            
766
42
  g_object_get_property (G_OBJECT (puzzle), name, &value);
767
42
  if (g_value_get_string (&value) != NULL)
768
    {
769
20
      if (!g_strcmp0 (name, "license"))
770
1
        name = _IPUZ_X_LICENSE_TAG;
771
19
      else if (!g_strcmp0 (name, "locale"))
772
        name = _IPUZ_X_LOCALE_TAG;
773

            
774
20
      json_builder_set_member_name (builder, name);
775
20
      json_builder_add_string_value (builder, g_value_get_string (&value));
776
    }
777
42
  g_value_unset (&value);
778
42
}
779

            
780

            
781
static void
782
build_styles_foreach (const char  *style_name,
783
                      IPuzStyle   *style,
784
                      JsonBuilder *builder)
785
{
786
  g_return_if_fail (style_name != NULL);
787
  g_return_if_fail (style != NULL);
788

            
789
  json_builder_set_member_name (builder, style_name);
790
  ipuz_style_build (style, builder);
791
}
792

            
793
static void
794
2
build_styles (IPuzPuzzle  *puzzle,
795
              JsonBuilder *builder)
796
{
797
  IPuzPuzzlePrivate *priv;
798

            
799
2
  priv = ipuz_puzzle_get_instance_private (puzzle);
800
2
  if (priv->styles == NULL)
801
2
    return;
802

            
803
  json_builder_set_member_name (builder, "styles");
804
  json_builder_begin_object (builder);
805
  g_hash_table_foreach (priv->styles, (GHFunc) build_styles_foreach, builder);
806
  json_builder_end_object (builder);
807

            
808
}
809
static void
810
2
ipuz_puzzle_real_build (IPuzPuzzle  *puzzle,
811
                        JsonBuilder *builder)
812
{
813
  /* Print the version and kind */
814
2
  build_kind (puzzle, builder);
815

            
816
  /* Print the string properties */
817
  guint i;
818
50
  for (i = PROP_0 + 1; i < N_PROPS; i++)
819
    {
820
48
      if (G_PARAM_SPEC_VALUE_TYPE (obj_props[i]) == G_TYPE_STRING)
821
42
        build_string_param (puzzle, obj_props[i], builder);
822
    }
823

            
824
  /* Print puzzle-wide styles */
825
2
  build_styles (puzzle, builder);
826

            
827
  /* FIXME(checksum): write this */
828
  /* build_checksum (puzzle, builder); */
829
2
}
830

            
831
static IPuzPuzzleFlags
832
2
ipuz_puzzle_real_get_flags (IPuzPuzzle *puzzle)
833
{
834
  IPuzPuzzlePrivate *priv;
835
2
  guint flags = 0;
836

            
837
2
  priv = ipuz_puzzle_get_instance_private (puzzle);
838

            
839
2
  if (priv->checksums != NULL)
840
    flags |= IPUZ_PUZZLE_FLAG_HAS_CHECKSUM;
841

            
842
2
  return flags;
843
}
844

            
845
static void
846
2
ipuz_puzzle_real_calculate_info (IPuzPuzzle      *puzzle,
847
                                 IPuzPuzzleInfo  *info)
848
{
849
  IPuzPuzzlePrivate *priv;
850
  IPuzCharsetBuilder *builder;
851
2
  g_autofree gchar *charset_str = NULL;
852

            
853
2
  g_assert (IPUZ_IS_PUZZLE (puzzle));
854
2
  g_assert (IPUZ_IS_PUZZLE_INFO (info));
855

            
856
2
  priv = ipuz_puzzle_get_instance_private (puzzle);
857

            
858
2
  charset_str = ipuz_charset_serialize (ipuz_puzzle_get_charset (puzzle));
859
2
  builder = ipuz_charset_builder_new_from_text (charset_str);
860
2
  info->charset = ipuz_charset_builder_build (builder);
861

            
862
2
  if (priv->checksums != NULL)
863
    info->flags |= IPUZ_PUZZLE_FLAG_HAS_CHECKSUM;
864
2
}
865

            
866
static void
867
styles_copy_func (const gchar *key,
868
                  IPuzStyle   *style,
869
                  GHashTable  *dest_styles)
870
{
871
  IPuzStyle *new_style;
872

            
873
  new_style = ipuz_style_copy (style);
874
  g_hash_table_insert (dest_styles, g_strdup (key), new_style);
875
}
876

            
877
static void
878
2
ipuz_puzzle_real_clone (IPuzPuzzle *src,
879
                        IPuzPuzzle *dest)
880
{
881
  IPuzPuzzlePrivate *src_priv, *dest_priv;
882

            
883
2
  g_return_if_fail (dest != NULL);
884

            
885
2
  src_priv = ipuz_puzzle_get_instance_private (src);
886
2
  dest_priv = ipuz_puzzle_get_instance_private (dest);
887

            
888
2
  g_clear_pointer (&dest_priv->version, g_free);
889
2
  dest_priv->version = g_strdup (src_priv->version);
890

            
891
2
  g_clear_pointer (&dest_priv->copyright, g_free);
892
2
  dest_priv->copyright = g_strdup (src_priv->copyright);
893

            
894
2
  g_clear_pointer (&dest_priv->publisher, g_free);
895
2
  dest_priv->publisher = g_strdup (src_priv->publisher);
896

            
897
2
  g_clear_pointer (&dest_priv->publication, g_free);
898
2
  dest_priv->publication = g_strdup (src_priv->publication);
899

            
900
2
  g_clear_pointer (&dest_priv->url, g_free);
901
2
  dest_priv->url = g_strdup (src_priv->url);
902

            
903
2
  g_clear_pointer (&dest_priv->uniqueid, g_free);
904
2
  dest_priv->uniqueid = g_strdup (src_priv->uniqueid);
905

            
906
2
  g_clear_pointer (&dest_priv->title, g_free);
907
2
  dest_priv->title = g_strdup (src_priv->title);
908

            
909
2
  g_clear_pointer (&dest_priv->intro, g_free);
910
2
  dest_priv->intro = g_strdup (src_priv->intro);
911

            
912
2
  g_clear_pointer (&dest_priv->explanation, g_free);
913
2
  dest_priv->explanation = g_strdup (src_priv->explanation);
914

            
915
2
  g_clear_pointer (&dest_priv->annotation, g_free);
916
2
  dest_priv->annotation = g_strdup (src_priv->annotation);
917

            
918
2
  g_clear_pointer (&dest_priv->author, g_free);
919
2
  dest_priv->author = g_strdup (src_priv->author);
920

            
921
2
  g_clear_pointer (&dest_priv->editor, g_free);
922
2
  dest_priv->editor = g_strdup (src_priv->editor);
923

            
924
2
  g_clear_pointer (&dest_priv->date, g_free);
925
2
  dest_priv->date = g_strdup (src_priv->date);
926

            
927
2
  g_clear_pointer (&dest_priv->notes, g_free);
928
2
  dest_priv->notes = g_strdup (src_priv->notes);
929

            
930
2
  g_clear_pointer (&dest_priv->difficulty, g_free);
931
2
  dest_priv->difficulty = g_strdup (src_priv->difficulty);
932

            
933
2
  g_clear_pointer (&dest_priv->charset_str, g_free);
934
2
  dest_priv->charset_str = g_strdup (src_priv->charset_str);
935

            
936
2
  g_clear_pointer (&dest_priv->origin, g_free);
937
2
  dest_priv->origin = g_strdup (src_priv->origin);
938

            
939
2
  g_clear_pointer (&dest_priv->block, g_free);
940
2
  dest_priv->block = g_strdup (src_priv->block);
941

            
942
2
  g_clear_pointer (&dest_priv->empty, g_free);
943
2
  dest_priv->empty = g_strdup (src_priv->empty);
944

            
945
2
  g_clear_pointer (&dest_priv->styles, g_hash_table_unref);
946
2
  if (src_priv->styles)
947
    {
948
      dest_priv->styles = new_styles_hash_table ();
949
      g_hash_table_foreach (src_priv->styles, (GHFunc) styles_copy_func, dest_priv->styles);
950
    }
951

            
952
2
  g_clear_pointer (&dest_priv->license, g_free);
953
2
  dest_priv->license = g_strdup (src_priv->license);
954

            
955
2
  g_clear_pointer (&dest_priv->locale, g_free);
956
2
  dest_priv->locale = g_strdup (src_priv->locale);
957

            
958
2
  g_clear_pointer (&dest_priv->checksum_salt, g_free);
959
2
  dest_priv->checksum_salt = g_strdup (src_priv->checksum_salt);
960

            
961
2
  g_clear_pointer (&dest_priv->checksums, g_strfreev);
962
2
  dest_priv->checksums = g_strdupv (src_priv->checksums);
963

            
964
2
  ensure_charset (dest);
965
}
966

            
967
static const char * const *
968
ipuz_puzzle_real_get_kind_str (IPuzPuzzle *puzzle)
969
{
970
  return NULL;
971
}
972

            
973
static void
974
3
ipuz_puzzle_real_set_style (IPuzPuzzle *puzzle,
975
                            const char *style_name,
976
                            IPuzStyle  *style)
977
{
978
  IPuzPuzzlePrivate *priv;
979

            
980
3
  g_return_if_fail (IPUZ_IS_PUZZLE (puzzle));
981

            
982
3
  priv = ipuz_puzzle_get_instance_private (puzzle);
983

            
984
3
  if (priv->styles == NULL)
985
    priv->styles = new_styles_hash_table ();
986

            
987
3
  if (style == NULL)
988
    g_hash_table_remove (priv->styles, style_name);
989
  else
990
3
    g_hash_table_replace (priv->styles, g_strdup (style_name), ipuz_style_ref (style));
991
}
992

            
993
static gboolean
994
3
ipuz_puzzle_real_equal (IPuzPuzzle *puzzle_a,
995
                        IPuzPuzzle *puzzle_b)
996
{
997
  IPuzPuzzlePrivate *priv_a, *priv_b;
998
3
  priv_a = ipuz_puzzle_get_instance_private (puzzle_a);
999
3
  priv_b = ipuz_puzzle_get_instance_private (puzzle_b);
3
  if (priv_a->checksums)
    {
      if (priv_b->checksums == NULL)
        return FALSE;
      for (int i = 0; priv_a->checksums[i] != NULL; i++)
        {
          if (g_strcmp0 (priv_a->checksums[i], priv_b->checksums[i]) != 0)
            return FALSE;
        }
    }
3
  else if (priv_b->checksums)
    return FALSE;
3
  if (priv_a->styles)
    {
      if (priv_b->styles == NULL)
        return FALSE;
      GHashTableIter iter;
      gpointer key, value;
      g_hash_table_iter_init (&iter, priv_a->styles);
      while (g_hash_table_iter_next (&iter, &key, &value))
        {
          if (! ipuz_style_equal (g_hash_table_lookup (priv_b->styles, key), value))
            return FALSE;
        }
    }
3
  else if (priv_b->styles)
    return FALSE;
3
  return (!g_strcmp0 (priv_a->version, priv_b->version)
3
          && !g_strcmp0 (priv_a->copyright, priv_b->copyright)
3
          && !g_strcmp0 (priv_a->publisher, priv_b->publisher)
3
          && !g_strcmp0 (priv_a->publication, priv_b->publication)
3
          && !g_strcmp0 (priv_a->url, priv_b->url)
3
          && !g_strcmp0 (priv_a->uniqueid, priv_b->uniqueid)
3
          && !g_strcmp0 (priv_a->title, priv_b->title)
3
          && !g_strcmp0 (priv_a->intro, priv_b->intro)
3
          && !g_strcmp0 (priv_a->explanation, priv_b->explanation)
3
          && !g_strcmp0 (priv_a->annotation, priv_b->annotation)
3
          && !g_strcmp0 (priv_a->author, priv_b->author)
3
          && !g_strcmp0 (priv_a->editor, priv_b->editor)
3
          && !g_strcmp0 (priv_a->date, priv_b->date)
3
          && !g_strcmp0 (priv_a->notes, priv_b->notes)
3
          && !g_strcmp0 (priv_a->difficulty, priv_b->difficulty)
3
          && !g_strcmp0 (priv_a->charset_str, priv_b->charset_str)
3
          && !g_strcmp0 (priv_a->origin, priv_b->origin)
3
          && !g_strcmp0 (priv_a->block, priv_b->block)
3
          && !g_strcmp0 (priv_a->empty, priv_b->empty)
3
          && !g_strcmp0 (priv_a->license, priv_b->license)
3
          && !g_strcmp0 (priv_a->locale, priv_b->locale)
6
          && !g_strcmp0 (priv_a->checksum_salt, priv_b->checksum_salt));
}
/*
 * Helper functions
 */
/**
 * Confirm that the version is one we can support.
 */
static const gchar *
32
ipuz_puzzle_valid_puzzle_version (JsonNode  *root,
                                  GError   **error)
{
32
  g_return_val_if_fail (error !=NULL && *error == NULL, NULL);
64
  g_autoptr (JsonPath) path = json_path_new ();
32
  json_path_compile (path, "$.version", NULL);
64
  g_autoptr (JsonNode) result = json_path_match (path, root);
32
  if (result == NULL)
    {
      *error = g_error_new (IPUZ_ERROR, IPUZ_ERROR_INVALID_FILE, _("Missing version tag."));
      return NULL;
    }
32
  JsonArray *arr = json_node_get_array (result);
32
  JsonNode *element = json_array_get_element (arr, 0);
32
  const gchar *str = json_node_get_string (element);
  /* I don't know what version 1 really is, but we see them in the
   * wild occasionally and they seem to load fine */
32
  if (g_strcmp0 (str, _IPUZ_VERSION_1) == 0)
    return _IPUZ_VERSION_1;
32
  else if (g_strcmp0 (str, _IPUZ_VERSION_2) == 0)
32
    return _IPUZ_VERSION_2;
  *error = g_error_new (IPUZ_ERROR, IPUZ_ERROR_WRONG_VERSION, _("Unhandled version: %s"), str);
  return NULL;
}
gboolean
219
check_kind_version (const gchar *str,
                    const gchar *prefix,
                    gint         version)
{
  size_t len;
219
  g_return_val_if_fail (str != NULL, FALSE);
219
  g_return_val_if_fail (prefix != NULL, FALSE);
219
  len = strlen (prefix);
219
  if (strncmp (str, prefix, len) == 0)
    {
42
      if (str[len] == '#') /* Check for an optional version */
        {
42
          guint64 puz_version = g_ascii_strtoull (str + (len + 1), NULL, 10);
42
          if ((gint) puz_version <= version)
42
            return TRUE;
        }
      else if (! str[len]) /* We support unversioned puzzles */
        return TRUE;
    }
177
  return FALSE;
}
/**
 * Figure out what kind of puzzle
 */
static IPuzPuzzleKind
32
ipuz_puzzle_parse_kind (JsonNode  *root,
                        GError   **error)
{
32
  g_autoptr (JsonPath) path = NULL;
32
  g_autoptr (JsonNode) result = NULL;
  JsonArray *array;
32
  IPuzPuzzleKind kind = IPUZ_PUZZLE_UNKNOWN;
  /* We use json path as we're not ready to walk the tree yet */
32
  path = json_path_new ();
32
  json_path_compile (path, "$.kind[*]", NULL);
32
  result = json_path_match (path, root);
32
  if (result == NULL)
    {
      *error = g_error_new (IPUZ_ERROR, IPUZ_ERROR_INVALID_FILE, _("Missing the kind tag. This doesn't look like an ipuz file."));
      return IPUZ_PUZZLE_UNKNOWN;
    }
32
  array = json_node_get_array (result);
74
  for (guint i = 0; i < json_array_get_length (array); i++)
    {
      JsonNode *element;
      const gchar *str;
42
      element = json_array_get_element (array, i);
42
      if (!JSON_NODE_HOLDS_VALUE (element))
        continue;
42
      str = json_node_get_string (element);
42
      if (str == NULL)
        continue;
42
      if (check_kind_version (str, _IPUZ_ARROWWORD_PREFIX, _IPUZ_ARROWWORD_VERSION))
        {
          if (kind == IPUZ_PUZZLE_UNKNOWN ||
              kind == IPUZ_PUZZLE_CROSSWORD)
            kind = IPUZ_PUZZLE_ARROWWORD;
          continue;
        }
42
      else if (check_kind_version (str, _IPUZ_BARRED_PREFIX, _IPUZ_BARRED_VERSION))
        {
3
          if (kind == IPUZ_PUZZLE_UNKNOWN ||
              kind == IPUZ_PUZZLE_CROSSWORD)
3
            kind = IPUZ_PUZZLE_BARRED;
3
          continue;
        }
39
      else if (check_kind_version (str, _IPUZ_FILIPPINE_PREFIX, _IPUZ_FILIPPINE_VERSION))
        {
          if (kind == IPUZ_PUZZLE_UNKNOWN ||
              kind == IPUZ_PUZZLE_CROSSWORD)
            kind = IPUZ_PUZZLE_FILIPPINE;
          continue;
        }
39
      else if (check_kind_version (str, _IPUZ_CRYPTIC_PREFIX, _IPUZ_CRYPTIC_VERSION))
        {
7
          if (kind == IPUZ_PUZZLE_UNKNOWN ||
              kind == IPUZ_PUZZLE_CROSSWORD)
7
            kind = IPUZ_PUZZLE_CRYPTIC;
7
          continue;
        }
32
      else if (check_kind_version (str, _IPUZ_ACROSTIC_PREFIX, _IPUZ_ACROSTIC_VERSION))
        {
7
          if (kind == IPUZ_PUZZLE_UNKNOWN ||
	      kind == IPUZ_PUZZLE_CROSSWORD)
7
            kind = IPUZ_PUZZLE_ACROSTIC;
7
          continue;
        }
25
      else if (check_kind_version (str, _IPUZ_CROSSWORD_PREFIX, _IPUZ_CROSSWORD_VERSION))
        {
25
          if (kind == IPUZ_PUZZLE_UNKNOWN)
25
            kind = IPUZ_PUZZLE_CROSSWORD;
25
          continue;
        }
    }
32
  return kind;
}
static void
358
ipuz_puzzle_new_foreach (JsonObject  *object,
                         const gchar *member_name,
                         JsonNode    *member_node,
                         IPuzPuzzle  *puzzle)
{
  IPuzPuzzleClass *klass;
358
  klass = IPUZ_PUZZLE_GET_CLASS (puzzle);
358
  g_return_if_fail (klass->load_node != NULL);
358
  klass->load_node (puzzle, member_name, member_node);
}
static void
358
ipuz_puzzle_new_foreach_post (JsonObject  *object,
                              const gchar *member_name,
                              JsonNode    *member_node,
                              IPuzPuzzle  *puzzle)
{
  IPuzPuzzleClass *klass;
358
  klass = IPUZ_PUZZLE_GET_CLASS (puzzle);
358
  if (klass->post_load_node)
358
    klass->post_load_node (puzzle, member_name, member_node);
358
}
IPuzPuzzle *
32
ipuz_puzzle_new_from_json (JsonNode  *root,
                           GError   **error)
{
32
  GError *tmp_error = NULL;
32
  IPuzPuzzle *puzzle = NULL;
  IPuzPuzzleKind kind;
  const gchar *ipuz_version;
32
  g_return_val_if_fail (root != NULL, NULL);
32
  if (!JSON_NODE_HOLDS_OBJECT (root))
    {
      if (error)
        *error = g_error_new (IPUZ_ERROR, IPUZ_ERROR_INVALID_FILE, "The first element isn't an object");
      return NULL;
    }
32
  ipuz_version = ipuz_puzzle_valid_puzzle_version (root, &tmp_error);
32
  if (tmp_error != NULL)
    {
      g_propagate_error (error, tmp_error);
      return NULL;
    }
32
  kind = ipuz_puzzle_parse_kind (root, &tmp_error);
32
  if (tmp_error != NULL)
    {
      g_propagate_error (error, tmp_error);
      return NULL;
    }
32
  if (kind == IPUZ_PUZZLE_UNKNOWN)
    {
      if (error)
        *error = g_error_new (IPUZ_ERROR, IPUZ_ERROR_INVALID_FILE, "Unknown puzzle type");
      return NULL;
    }
32
  switch (kind)
    {
15
    case IPUZ_PUZZLE_CROSSWORD:
15
      puzzle = g_object_new (IPUZ_TYPE_CROSSWORD,
                             "version", ipuz_version,
                             NULL);
15
      break;
    case IPUZ_PUZZLE_ARROWWORD:
      puzzle = g_object_new (IPUZ_TYPE_ARROWWORD,
                             "version", ipuz_version,
                             NULL);
      break;
3
    case IPUZ_PUZZLE_BARRED:
3
      puzzle = g_object_new (IPUZ_TYPE_BARRED,
                             "version", ipuz_version,
                             NULL);
3
      break;
7
    case IPUZ_PUZZLE_CRYPTIC:
7
      puzzle = g_object_new (IPUZ_TYPE_CRYPTIC,
                             "version", ipuz_version,
                             NULL);
7
      break;
    case IPUZ_PUZZLE_FILIPPINE:
      puzzle = g_object_new (IPUZ_TYPE_FILIPPINE,
                             "version", ipuz_version,
                             NULL);
      break;
7
    case IPUZ_PUZZLE_ACROSTIC:
7
      puzzle = g_object_new (IPUZ_TYPE_ACROSTIC,
                             "version", ipuz_version,
                             NULL);
7
      break;
    default:
      g_assert_not_reached ();
    }
  /* Load the puzzle as best as we can */
32
  IPuzPuzzleClass *klass = IPUZ_PUZZLE_GET_CLASS (puzzle);
32
  JsonObject *object = json_node_get_object (root);
32
  g_object_freeze_notify (G_OBJECT (puzzle));
  /* We do a multi-pass parse. See parsing-strategy.md for more information */
32
  json_object_foreach_member (object, (JsonObjectForeach) ipuz_puzzle_new_foreach, puzzle);
32
  json_object_foreach_member (object, (JsonObjectForeach) ipuz_puzzle_new_foreach_post, puzzle);
32
  klass->fixup (puzzle);
32
  klass->validate (puzzle);
32
  g_object_thaw_notify (G_OBJECT (puzzle));
32
  return puzzle;
}
IPuzPuzzle *
30
ipuz_puzzle_new_from_file (const char  *filename,
                           GError     **error)
{
30
  GError *tmp_error = NULL;
30
  g_autoptr(JsonParser) parser = NULL;
30
  g_return_val_if_fail (filename != NULL, NULL);
30
  parser = json_parser_new ();
30
  json_parser_load_from_file (parser, filename, &tmp_error);
30
  if (tmp_error != NULL)
    {
      g_propagate_error (error, tmp_error);
      return NULL;
    }
30
  return ipuz_puzzle_new_from_json (json_parser_get_root (parser), error);
}
IPuzPuzzle *
ipuz_puzzle_new_from_stream (GInputStream  *stream,
                             GCancellable  *cancellable,
                             GError       **error)
{
  GError *tmp_error = NULL;
  g_autoptr(JsonParser) parser = NULL;
  g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL);
  parser = json_parser_new ();
  json_parser_load_from_stream (parser, stream, cancellable, &tmp_error);
  if (tmp_error != NULL)
    {
      g_propagate_error (error, tmp_error);
      return NULL;
    }
  return ipuz_puzzle_new_from_json (json_parser_get_root (parser), error);
}
IPuzPuzzle *
2
ipuz_puzzle_new_from_data (const gchar   *data,
                           gsize          length,
                           GError       **error)
{
2
  GError *tmp_error = NULL;
2
  g_autoptr(JsonParser) parser = NULL;
2
  g_return_val_if_fail (data != NULL, NULL);
2
  parser = json_parser_new ();
2
  json_parser_load_from_data (parser, data, length, &tmp_error);
2
  if (tmp_error != NULL)
    {
      g_propagate_error (error, tmp_error);
      return NULL;
    }
2
  return ipuz_puzzle_new_from_json (json_parser_get_root (parser), error);
}
static JsonGenerator *
2
ipuz_puzzle_get_generator (IPuzPuzzle *puzzle)
{
2
  g_autoptr (JsonBuilder) builder = NULL;
  JsonGenerator *generator;
  IPuzPuzzleClass *klass;
4
  g_autoptr (JsonNode) root = NULL;
2
  klass = IPUZ_PUZZLE_GET_CLASS (puzzle);
2
  builder = json_builder_new ();
2
  json_builder_begin_object (builder);
2
  generator = json_generator_new ();
2
  json_generator_set_pretty (generator, TRUE);
2
  if (klass->build)
2
    klass->build (puzzle, builder);
2
  json_builder_end_object (builder);
2
  root = json_builder_get_root (builder);
2
  json_generator_set_root (generator, root);
2
  return generator;
}
gboolean
ipuz_puzzle_save_to_file (IPuzPuzzle  *puzzle,
                          const char  *filename,
                          GError     **error)
{
  g_autoptr (JsonGenerator) generator = NULL;
  g_return_val_if_fail (IPUZ_IS_PUZZLE (puzzle), FALSE);
  generator = ipuz_puzzle_get_generator (puzzle);
  return json_generator_to_file (generator, filename, error);
}
gboolean
ipuz_puzzle_save_to_stream (IPuzPuzzle     *puzzle,
                            GOutputStream  *stream,
                            GCancellable   *cancellable,
                            GError        **error)
{
  g_autoptr (JsonGenerator) generator = NULL;
  g_return_val_if_fail (IPUZ_IS_PUZZLE (puzzle), FALSE);
  g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE);
  generator = ipuz_puzzle_get_generator (puzzle);
  return json_generator_to_stream (generator, stream, cancellable, error);
}
gchar *
2
ipuz_puzzle_save_to_data (IPuzPuzzle *puzzle,
                          gsize      *length)
{
2
  g_autoptr (JsonGenerator) generator = NULL;
2
  g_return_val_if_fail (IPUZ_IS_PUZZLE (puzzle), NULL);
2
  generator = ipuz_puzzle_get_generator (puzzle);
2
  return json_generator_to_data (generator, length);
}
/**
 * ipuz_puzzle_get_puzzle_kind:
 * @self: An #IPuzPuzzle
 *
 * Returns the type of puzzle of @self. This can be more useful than
 * the GType for switch statements or other conditionals.
 *
 * Note, this only returns the more specific type. So a cryptic
 * crossword will return #IPUZ_PUZZLE_CRYPTIC and not
 * #IPUZ_PUZZLE_CROSSWORD.
 *
 * Returns: The puzzle kind
 **/
IPuzPuzzleKind
ipuz_puzzle_get_puzzle_kind (IPuzPuzzle *self)
{
  g_return_val_if_fail (IPUZ_IS_PUZZLE (self), IPUZ_PUZZLE_UNKNOWN);
  if (IPUZ_IS_ACROSTIC (self))
    return IPUZ_PUZZLE_ACROSTIC;
  else if (IPUZ_IS_ARROWWORD (self))
    return IPUZ_PUZZLE_ARROWWORD;
  else if (IPUZ_IS_BARRED (self))
    return IPUZ_PUZZLE_BARRED;
  else if (IPUZ_IS_CRYPTIC (self))
    return IPUZ_PUZZLE_CRYPTIC;
  else if (IPUZ_IS_FILIPPINE (self))
    return IPUZ_PUZZLE_FILIPPINE;
  /* Crossword has to be last otherwise the ones that inherit from it
   * will trigger this boolean */
  else if (IPUZ_IS_CROSSWORD (self))
    return IPUZ_PUZZLE_CROSSWORD;
  return IPUZ_PUZZLE_UNKNOWN;
}
/**
 * ipuz_puzzle_get_flags:
 * @puzzle: An `IPuzPuzzle`
 *
 * Indicates static properties about the puzzle at load time.
 *
 * Returns: flags, with properties about the puzzle
 **/
IPuzPuzzleFlags
2
ipuz_puzzle_get_flags (IPuzPuzzle *puzzle)
{
  IPuzPuzzleClass *klass;
2
  g_return_val_if_fail (IPUZ_IS_PUZZLE (puzzle), 0);
2
  klass = IPUZ_PUZZLE_GET_CLASS (puzzle);
2
  return klass->get_flags (puzzle);
}
/**
 * ipuz_puzzle_get_info:
 * @puzzle: An `IPuzPuzzle`
 *
 * Calculates information about the current state of @puzzle. This
 * information is not kept in sync with the puzzle, so if there are
 * any changes to it it will have to be recalculated.
 *
 * Returns: a newly allocated `IPuzPuzzleInfo` object
 **/
IPuzPuzzleInfo *
2
ipuz_puzzle_get_info (IPuzPuzzle *puzzle)
{
  IPuzPuzzleClass *klass;
  IPuzPuzzleInfo *info;
2
  g_return_val_if_fail (IPUZ_IS_PUZZLE (puzzle), 0);
2
  klass = IPUZ_PUZZLE_GET_CLASS (puzzle);
2
  info = g_object_new (IPUZ_TYPE_PUZZLE_INFO, NULL);
2
  klass->calculate_info (puzzle, info);
2
  return info;
}
IPuzPuzzle *
2
ipuz_puzzle_deep_copy (IPuzPuzzle *src)
{
  IPuzPuzzle *dest;
  IPuzPuzzleClass *klass;
  /* We allow NULL puzzles to return */
2
  if (src == NULL)
    return NULL;
2
  g_return_val_if_fail (IPUZ_IS_PUZZLE (src), NULL);
2
  dest = g_object_new (G_TYPE_FROM_INSTANCE (src), NULL);
2
  klass = IPUZ_PUZZLE_GET_CLASS (src);
2
  klass->clone (src, dest);
2
  return dest;
}
gboolean
3
ipuz_puzzle_equal (IPuzPuzzle *puzzle_a,
                   IPuzPuzzle *puzzle_b)
{
  IPuzPuzzleClass *klass;
3
  g_return_val_if_fail (IPUZ_IS_PUZZLE (puzzle_a), FALSE);
3
  g_return_val_if_fail (IPUZ_IS_PUZZLE (puzzle_b), FALSE);
3
  klass = IPUZ_PUZZLE_GET_CLASS (puzzle_a);
3
  return klass->equal (puzzle_a, puzzle_b);
}
/* Getters and Setters */
static void
403
ensure_charset (IPuzPuzzle *self)
{
  IPuzPuzzlePrivate *priv;
403
  IPuzCharsetBuilder *builder = NULL;
403
  priv = ipuz_puzzle_get_instance_private (self);
403
  if (priv->charset && priv->charset_str)
364
    return;
39
  if (priv->charset == NULL)
    {
39
      if (priv->charset_str)
        builder = ipuz_charset_builder_new_from_text (priv->charset_str);
39
      else if (priv->locale)
        builder = ipuz_charset_builder_new_for_language (priv->locale);
      else
39
        builder = ipuz_charset_builder_new_for_language ("C");
39
      priv->charset = ipuz_charset_builder_build (builder);
    }
39
  if (priv->charset_str == NULL)
39
    priv->charset_str = ipuz_charset_serialize (priv->charset);
}
IPuzCharset *
326
ipuz_puzzle_get_charset (IPuzPuzzle *self)
{
  IPuzPuzzlePrivate *priv;
326
  g_return_val_if_fail (IPUZ_IS_PUZZLE (self), NULL);
326
  priv = ipuz_puzzle_get_instance_private (self);
326
  ensure_charset (self);
326
  return priv->charset;
}
void
ipuz_puzzle_set_charset (IPuzPuzzle  *self,
                         IPuzCharset *charset)
{
  IPuzPuzzlePrivate *priv;
  g_return_if_fail (IPUZ_IS_PUZZLE (self));
  priv = ipuz_puzzle_get_instance_private (self);
  g_clear_pointer (&priv->charset_str, g_free);
  g_clear_pointer (&priv->charset, ipuz_charset_unref);
  priv->charset = ipuz_charset_ref (charset);
  ensure_charset (self);
}
const gchar *
36
ipuz_puzzle_get_charset_str (IPuzPuzzle *self)
{
  IPuzPuzzlePrivate *priv;
36
  g_return_val_if_fail (IPUZ_IS_PUZZLE (self), NULL);
36
  priv = ipuz_puzzle_get_instance_private (self);
36
  ensure_charset (self);
36
  return priv->charset_str;
}
void
4
ipuz_puzzle_set_charset_str (IPuzPuzzle  *self,
                             const gchar *charset_str)
{
  IPuzPuzzlePrivate *priv;
4
  g_return_if_fail (IPUZ_IS_PUZZLE (self));
4
  priv = ipuz_puzzle_get_instance_private (self);
4
  if (!g_strcmp0 (priv->charset_str, charset_str))
4
    return;
  g_clear_pointer (&priv->charset_str, g_free);
  g_clear_pointer (&priv->charset, ipuz_charset_unref);
  priv->charset_str = g_strdup (charset_str);
  ensure_charset (self);
}
IPuzStyle *
102
ipuz_puzzle_get_style (IPuzPuzzle  *self,
                       const gchar *style_name)
{
  IPuzPuzzlePrivate *priv;
102
  g_return_val_if_fail (IPUZ_IS_PUZZLE (self), NULL);
102
  priv = ipuz_puzzle_get_instance_private (self);
102
  if (priv->styles)
102
    return (IPuzStyle *) g_hash_table_lookup (priv->styles, style_name);
  return NULL;
}
void
3
ipuz_puzzle_set_style (IPuzPuzzle  *self,
                       const gchar *style_name,
                       IPuzStyle   *style)
{
  IPuzPuzzleClass *klass;
3
  g_return_if_fail (IPUZ_IS_PUZZLE (self));
3
  klass = IPUZ_PUZZLE_GET_CLASS (self);
3
  klass->set_style (self, style_name, style);
}
void
5
ipuz_puzzle_style_foreach (IPuzPuzzle           *self,
                           IPuzStyleForeachFunc  func,
                           gpointer              user_data)
{
  IPuzPuzzlePrivate *priv;
5
  g_return_if_fail (IPUZ_IS_PUZZLE (self));
5
  priv = ipuz_puzzle_get_instance_private (self);
5
  if (priv->styles)
5
    g_hash_table_foreach (priv->styles, (GHFunc) func, user_data);
}