diff --git a/.all-contributorsrc b/.all-contributorsrc index 3b8675da22..02f07de694 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -73,6 +73,16 @@ "contributions": [ "code" ] + }, + { + "login": "wyz2368", + "name": "wyz2368", + "avatar_url": "https://avatars.githubusercontent.com/u/25018133?v=4", + "profile": "https://github.com/wyz2368", + "contributions": [ + "code", + "research" + ] } ] } diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4e01ff6baa..5b88b5f528 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -45,7 +45,7 @@ jobs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - uses: actions/checkout@v7 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.14" - uses: py-actions/flake8@v2 @@ -56,7 +56,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: "3.14" - name: Install Python packages diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 76e40c4ea3..9282035009 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Set up dependencies @@ -44,7 +44,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Set up dependencies @@ -67,7 +67,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Set up dependencies @@ -90,7 +90,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Set up dependencies diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4d4ef171a..a04ca91f56 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: create-release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Extract changelog for this version run: | diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 96e0ca65f8..fc6e79b78f 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: '3.x' - name: Set up dependencies diff --git a/AUTHORS b/AUTHORS index dd2de970e5..89d0485035 100644 --- a/AUTHORS +++ b/AUTHORS @@ -57,10 +57,3 @@ wxWidgets, which are Copyright (C) 2004-5 by John Labenski, and distributed under the wxWidgets license. The version included here is the version dated 20 July 2005; see http://wxcode.sourceforge.net for details on these classes. - -TinyXML parser: ---------------- -The graphical interface reads and writes files in XML format. The parsing -is done using the TinyXml package from http://www.grinninglizard.com/tinyxml, -and is distributed under the zlib license. These are the files named -tiny* in the sources/gui directory of the distribution. diff --git a/ChangeLog b/ChangeLog index bca1fd127a..19e30ae56e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,18 @@ -## [16.7.0] - unreleased +## [17.0.0] - unreleased + +### Changed +- `lcp_solve` no longer silently absorbs internal exceptions if they occur, but instead these propagate out + to match the behaviour of all other Nash equilibrium solvers. (#996) +- `gnm_solve` executes callback when equilibrium is found rather than emitting all equilibria at the end of + the run. (#998) +- Implemented bespoke XML parser that handles the subset in the de-facto legacy XML workbook .gbt format; + removes dependency on tinyxml. (#897) + +### Fixed +- Converting a behavior profile to a strategy profile, and computing pure-strategy payoffs + no longer inserts spurious entries into a strategy's internal action map. + +## [16.7.0] - 2026-07-11 ### Added - Added `Player.sequences` as the collection of sequences available to a player. @@ -25,6 +39,9 @@ - Corrected calculation of total number of actions for a player in `pygambit` (#938) - Corrected a regression in action graph games that left the internal data structure not fully initialised, leading to segmentation faults. +- Corrected handling of malformed AGG/BAGG files: files with an invalid or degenerate header + (for example a wrong file type, or a header declaring zero players) are now rejected with a + `ValueError` instead of causing a segmentation fault while constructing the game. ### Changed - Added a new welcome/landing window on launching the GUI without a game. This has the effect of @@ -39,6 +56,9 @@ - In `pygambit`, indexing game object collections by integer position has been removed. (#942) - Validity of game object labels is enforced (printable ASCII and spaces only, no leading/trailing or double spaces); invalid labels raise `ValueError` in `pygambit`. (#944) +- Nonempty labels are now required for all players, outcomes, actions, and strategies. + Labels must be unique within the game for players and outcomes, within the player for + strategies, and within the information set for actions. - Refined and clarified the graphical interface's handling (and persisting) of "workspaces", and improved warning messages on closing windows for games or workspaces with unsaved changes. - In `pygambit`, the `.outcome`, `.player`, and `.infoset` attributes are now treated as predicates diff --git a/Makefile.am b/Makefile.am index fde0f3ca11..f606d7eba3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -223,11 +223,7 @@ core_SOURCES = \ src/core/rational.h \ src/core/matrix.cc \ src/core/function.cc \ - src/core/function.h \ - src/core/tinyxml.cc \ - src/core/tinyxmlerror.cc \ - src/core/tinyxmlparser.cc \ - src/core/tinyxml.h + src/core/function.h agg_SOURCES = \ src/games/gameagg.cc \ @@ -270,6 +266,8 @@ game_SOURCES = \ src/games/stratpure.h \ src/games/stratmixed.h \ src/games/file.cc \ + src/games/workspace.cc \ + src/games/workspace.h \ src/games/writer.cc \ src/games/writer.h \ src/games/layout.cc \ @@ -477,8 +475,12 @@ gambit_SOURCES = \ src/gui/analysis.h \ src/gui/app.cc \ src/gui/app.h \ + src/gui/editlabel.cc \ + src/gui/editlabel.h \ src/gui/edittext.cc \ src/gui/edittext.h \ + src/gui/labelcell.cc \ + src/gui/labelcell.h \ src/gui/dlabout.cc \ src/gui/dlabout.h \ src/gui/dleditmove.cc \ @@ -500,7 +502,7 @@ gambit_SOURCES = \ src/gui/dlnash.cc \ src/gui/dlnash.h \ src/gui/dlnashmon.cc \ - src/gui/dlnashmon.h \ + src/gui/nashspec.h \ src/gui/dlnewtable.cc \ src/gui/dlnewtable.h \ src/gui/dlnfglogit.cc \ diff --git a/README.md b/README.md index 69ed98074d..288c524587 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ installable via PyPI. StephenPasteris
StephenPasteris

🔬 💻 Daniel Kadnikov
Daniel Kadnikov

🔬 💻 Andrés Fernández Cervell
Andrés Fernández Cervell

💻 + wyz2368
wyz2368

💻 🔬 diff --git a/build_support/GAMBIT_VERSION b/build_support/GAMBIT_VERSION index bd015b9036..2e4239c313 100644 --- a/build_support/GAMBIT_VERSION +++ b/build_support/GAMBIT_VERSION @@ -1 +1 @@ -16.6.0 +16.7.0 diff --git a/build_support/catalog/catalog.am b/build_support/catalog/catalog.am index 58c225f429..5af16f3bcf 100644 --- a/build_support/catalog/catalog.am +++ b/build_support/catalog/catalog.am @@ -1,6 +1,24 @@ CATALOG_FILES = \ catalog/books/myerson1991/fig2_1.efg \ catalog/books/myerson1991/fig4_2.efg \ + catalog/books/shohamleytonbrown2008/fig5_1.efg \ + catalog/books/shohamleytonbrown2008/fig5_10.efg \ + catalog/books/shohamleytonbrown2008/fig5_10__original_layout.ef \ + catalog/books/shohamleytonbrown2008/fig5_11.efg \ + catalog/books/shohamleytonbrown2008/fig5_11__original_layout.ef \ + catalog/books/shohamleytonbrown2008/fig5_12.efg \ + catalog/books/shohamleytonbrown2008/fig5_12__original_layout.ef \ + catalog/books/shohamleytonbrown2008/fig5_15.efg \ + catalog/books/shohamleytonbrown2008/fig5_15__original_layout.ef \ + catalog/books/shohamleytonbrown2008/fig5_1__original_layout.ef \ + catalog/books/shohamleytonbrown2008/fig5_2.efg \ + catalog/books/shohamleytonbrown2008/fig5_2__original_layout.ef \ + catalog/books/shohamleytonbrown2008/fig5_9.efg \ + catalog/books/shohamleytonbrown2008/fig5_9__original_layout.ef \ + catalog/books/shohamleytonbrown2008/fig6_2.efg \ + catalog/books/shohamleytonbrown2008/fig6_2__original_layout.ef \ + catalog/books/shohamleytonbrown2008/fig6_8.efg \ + catalog/books/shohamleytonbrown2008/fig6_8__original_layout.ef \ catalog/books/vonstengel2022/fig10.1.efg \ catalog/books/vonstengel2022/fig10.12.efg \ catalog/books/vonstengel2022/fig10.5.efg \ @@ -23,6 +41,7 @@ CATALOG_FILES = \ catalog/journals/ijgt/selten1975/fig2.efg \ catalog/journals/ijgt/selten1975/fig3.efg \ catalog/journals/mor/vonstengelforges2008/fig1.efg \ + catalog/journals/mor/vonstengelforges2008/fig1__Original_Layout.ef \ catalog/journals/mor/vonstengelforges2008/fig6.efg \ catalog/journals/mor/vonstengelforges2008/fig6__Original_Layout.ef \ catalog/journals/mor/vonstengelforges2008/fig9.efg \ diff --git a/build_support/catalog/catalog_hierarchy.yaml b/build_support/catalog/catalog_hierarchy.yaml index 9280493a6f..591f21320f 100644 --- a/build_support/catalog/catalog_hierarchy.yaml +++ b/build_support/catalog/catalog_hierarchy.yaml @@ -14,6 +14,7 @@ labels: books/myerson1991: "Myerson (1991) — Game Theory: Analysis of Conflict" books/vonstengel2022: "von Stengel (2022) — Game Theory Basics" books/watson2013: "Watson (2013) — Strategy: An Introduction to Game Theory" + books/shohamleytonbrown2008: "Shoham and Leyton-Brown (2008) — Multiagent Systems, Algorithmic, Game-Theoretic, and Logical Foundations" journals/geb/gilboa1997: "Gilboa (1997)" journals/geb/wichardt2008: "Wichardt (2008)" journals/ijgt/nau2004: "Nau et al. (2004)" diff --git a/build_support/catalog/gtdraw_settings.yaml b/build_support/catalog/gtdraw_settings.yaml index 2bbd9889ca..956b107fa7 100644 --- a/build_support/catalog/gtdraw_settings.yaml +++ b/build_support/catalog/gtdraw_settings.yaml @@ -44,3 +44,31 @@ overrides: 2: '#DD79E0' 3: '#1616BC' 4: '#985003' + + books/shohamleytonbrown2008/fig6_8: + scale_factor: 1.0 + level_scaling: 1.0 + sublevel_scaling: 1.0 + width_scaling: 1.0 + horizontal: false + mirror: false + shared_terminal_depth: false + color_scheme: colorblind + edge_thickness: 1.0 + action_label_position: 0.5 + action_label_position_by: player + action_label_dist: 1.0 + vary_action_label_positions: false + vary_action_label_positions_by: all + font_family: rmfamily + font_bold: false + font_italic: false + font_size: normalsize + node_size: 1.5 + label_bg: false + label_bg_color: white + label_bg_opacity: 0.8 + iset_fill: true + iset_fill_opacity: 0.2 + iset_boundary: none + legend_position: top-left diff --git a/build_support/catalog/test_update.py b/build_support/catalog/test_update.py index 645dc25f9b..e58c63f554 100644 --- a/build_support/catalog/test_update.py +++ b/build_support/catalog/test_update.py @@ -147,6 +147,57 @@ def _make_image_files(catalog_dir, slug, fmt="efg"): (img_dir / f"{slug}.ef").touch() +# --------------------------------------------------------------------------- +# Tests for catalog resource selection +# --------------------------------------------------------------------------- + + +@pytest.mark.catalog_update +class TestCatalogResourceSelection: + def test_catalog_games_uses_requested_catalog_dir_and_restores_resource( + self, tmp_path, monkeypatch + ): + stale_dir = tmp_path / "installed_catalog_data" + checkout_dir = tmp_path / "checkout_catalog" + stale_dir.mkdir() + checkout_dir.mkdir() + calls = [] + + def fake_games(**kwargs): + calls.append((update.gbt.catalog._CATALOG_RESOURCE, kwargs)) + return _make_df(_efg_row("checkout/game1")) + + monkeypatch.setattr(update.gbt.catalog, "_CATALOG_RESOURCE", stale_dir) + monkeypatch.setattr(update.gbt.catalog, "games", fake_games) + + df = update._catalog_games(checkout_dir) + + assert calls == [(checkout_dir, {"include_descriptions": True})] + assert stale_dir == update.gbt.catalog._CATALOG_RESOURCE + assert list(df["Game"]) == ["checkout/game1"] + + def test_generate_rst_table_uses_requested_catalog_dir_while_rendering( + self, tmp_path, monkeypatch + ): + stale_dir = tmp_path / "installed_catalog_data" + checkout_dir = tmp_path / "checkout_catalog" + stale_dir.mkdir() + checkout_dir.mkdir() + observed = [] + + def fake_write_tree_level(*args, **kwargs): + observed.append(update.gbt.catalog._CATALOG_RESOURCE) + + monkeypatch.setattr(update.gbt.catalog, "_CATALOG_RESOURCE", stale_dir) + monkeypatch.setattr(update, "load_hierarchy_labels", lambda: {}) + monkeypatch.setattr(update, "_write_tree_level", fake_write_tree_level) + + update.generate_rst_table(_make_df(), tmp_path / "out.rst", catalog_dir=checkout_dir) + + assert observed == [checkout_dir] + assert stale_dir == update.gbt.catalog._CATALOG_RESOURCE + + # --------------------------------------------------------------------------- # Tests for catalog_gtdraw_settings # --------------------------------------------------------------------------- diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index 0cf51385e9..5cc15443b7 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -1,6 +1,7 @@ import argparse import shutil import sys +from contextlib import contextmanager from pathlib import Path import pandas as pd @@ -17,6 +18,24 @@ SUPPORTED_GAME_FORMATS = {"efg", "nfg"} +@contextmanager +def _using_catalog_dir(catalog_dir: Path): + """Temporarily make pygambit.catalog read data from the checked-out catalog.""" + old_resource = gbt.catalog._CATALOG_RESOURCE + gbt.catalog._CATALOG_RESOURCE = catalog_dir + try: + yield + finally: + gbt.catalog._CATALOG_RESOURCE = old_resource + + +def _catalog_games(catalog_dir: Path | None = None) -> pd.DataFrame: + """Return catalog games using files from *catalog_dir*, not installed package data.""" + catalog_dir = catalog_dir or CATALOG_DIR + with _using_catalog_dir(catalog_dir): + return gbt.catalog.games(include_descriptions=True) + + def catalog_gtdraw_settings(slug: str) -> dict: """Return the gtdraw settings for a given catalog slug.""" with open(GTDRAW_SETTINGS_CONFIG, encoding="utf-8") as f: @@ -325,7 +344,7 @@ def generate_rst_table( catalog_dir = catalog_dir or CATALOG_DIR labels = load_hierarchy_labels() tree = _build_slug_tree(df) - with open(rst_path, "w", encoding="utf-8") as f: + with _using_catalog_dir(catalog_dir), open(rst_path, "w", encoding="utf-8") as f: _write_tree_level( f, tree, "", labels, catalog_dir, indent="", regenerate_images=regenerate_images ) @@ -409,7 +428,7 @@ def update_makefile( args = parser.parse_args() # Create RST list-table used by doc/catalog.rst - df = gbt.catalog.games(include_descriptions=True) + df = _catalog_games() _warn_missing_descriptions(df) generate_rst_table(df, CATALOG_RST_TABLE, regenerate_images=args.regenerate_images) print(f"Generated {CATALOG_RST_TABLE} for use in local docs build. DO NOT COMMIT.") diff --git a/catalog/books/shohamleytonbrown2008/fig5_1.efg b/catalog/books/shohamleytonbrown2008/fig5_1.efg new file mode 100644 index 0000000000..49f598d289 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_1.efg @@ -0,0 +1,21 @@ +EFG 2 R "Fig 5.1 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.1 from :cite:p:`ShoLeyB08`. +This is a sharing game. Imagine a brother and sister sharing two indivisible and identical presents +from their parents. First the brother suggests a split, which can be one of three: he +keeps both, she keeps both, or they each keep one. Then the sister chooses whether +to accept or reject the split. If she accepts they each get their allocated present(s), +and otherwise neither gets any gift. +" + +p "" 1 1 "" { "2-0" "1-1" "0-2" } 0 +p "" 2 2 "" { "no" "yes" } 0 +t "" 1 "" { 0 0 } +t "" 2 "" { 2 0 } +p "" 2 3 "" { "no" "yes" } 0 +t "" 3 "" { 0 0 } +t "" 4 "" { 1 1 } +p "" 2 4 "" { "no" "yes" } 0 +t "" 5 "" { 0 0 } +t "" 6 "" { 0 2 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_10.efg b/catalog/books/shohamleytonbrown2008/fig5_10.efg new file mode 100644 index 0000000000..94cd759ff3 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_10.efg @@ -0,0 +1,16 @@ +EFG 2 R "Fig 5.10 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.10 from :cite:p:`ShoLeyB08`. +This is an example of an imperfect-information game. +" + +p "" 1 2 "" { "L" "R" } 0 +p "" 2 3 "" { "A" "B" } 0 +p "" 1 1 "" { "l" "r" } 0 +t "" 1 "" { 0 0 } +t "" 2 "" { 2 4 } +p "" 1 1 0 +t "" 3 "" { 4 2 } +t "" 4 "" { 0 0 } +t "" 5 "" { 0 0 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_10__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_10__original_layout.ef new file mode 100644 index 0000000000..55e9832224 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_10__original_layout.ef @@ -0,0 +1,12 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 player 2 xshift -3.58 from 0,1 move L +level 2 node 2 xshift 3.58 from 0,1 move R payoffs 0 0 +level 4 node 1 xshift -2.86 from 2,1 move A +level 4 node 2 xshift 2.86 from 2,1 move B +level 6 node 1 xshift -1.43 from 4,1 move l payoffs 0 0 +level 6 node 2 xshift 1.43 from 4,1 move r payoffs 2 4 +level 6 node 3 xshift -1.43 from 4,2 move l payoffs 4 2 +level 6 node 4 xshift 1.43 from 4,2 move r payoffs 0 0 +iset 4,1 4,2 player 1 diff --git a/catalog/books/shohamleytonbrown2008/fig5_11.efg b/catalog/books/shohamleytonbrown2008/fig5_11.efg new file mode 100644 index 0000000000..a972fb90be --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_11.efg @@ -0,0 +1,14 @@ +EFG 2 R "Fig 5.11 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.11 from :cite:p:`ShoLeyB08`. +The Prisoner's Dilemma game in extensive form. +" + +p "" 1 2 "" { "C" "D" } 0 +p "" 2 1 "" { "c" "d" } 0 +t "" 1 "" { -1 -1 } +t "" 2 "" { -4 0 } +p "" 2 1 0 +t "" 3 "" { 0 -4 } +t "" 4 "" { -3 -3 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_11__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_11__original_layout.ef new file mode 100644 index 0000000000..8aa60582e2 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_11__original_layout.ef @@ -0,0 +1,10 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 xshift -3.58 from 0,1 move C +level 2 node 2 xshift 3.58 from 0,1 move D +level 4 node 1 xshift -1.79 from 2,1 move c payoffs -1 -1 +level 4 node 2 xshift 1.79 from 2,1 move d payoffs -4 0 +level 4 node 3 xshift -1.79 from 2,2 move c payoffs 0 -4 +level 4 node 4 xshift 1.79 from 2,2 move d payoffs -3 -3 +iset 2,1 2,2 player 2 diff --git a/catalog/books/shohamleytonbrown2008/fig5_12.efg b/catalog/books/shohamleytonbrown2008/fig5_12.efg new file mode 100644 index 0000000000..27c70afe2d --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_12.efg @@ -0,0 +1,15 @@ +EFG 2 R "Fig 5.12 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.12 from :cite:p:`ShoLeyB08`. +A game with imperfect recall, in particular absent-mindedness. +" + + +p "" 1 1 "" { "L" "R" } 0 +p "" 1 1 0 +t "" 1 "" { 1 0 } +t "" 2 "" { 100 100 } +p "" 2 2 "" { "U" "D" } 0 +t "" 3 "" { 5 1 } +t "" 4 "" { 2 2 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_12__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_12__original_layout.ef new file mode 100644 index 0000000000..98b720cdc2 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_12__original_layout.ef @@ -0,0 +1,15 @@ +% Extensive-form game transcribed from the uploaded image +player 1 name 1 +player 2 name 2 + +% Player 1's two decision nodes are in the same information set. +level 0 node 1 +level 2 node 1 xshift -3 from 0,1 move L +level 2 node 2 xshift 3 from 0,1 move R player 2 + +level 4 node 1 xshift -1 from 2,1 move L payoffs 1 0 +level 4 node 2 xshift 1 from 2,1 move R payoffs 100 100 +level 4 node 3 xshift -1 from 2,2 move U payoffs 5 1 +level 4 node 4 xshift 1 from 2,2 move D payoffs 2 2 + +iset 0,1 2,1 player 1 diff --git a/catalog/books/shohamleytonbrown2008/fig5_15.efg b/catalog/books/shohamleytonbrown2008/fig5_15.efg new file mode 100644 index 0000000000..ceb402d948 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_15.efg @@ -0,0 +1,17 @@ +EFG 2 R "Fig 5.15 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.15 from :cite:p:`ShoLeyB08`. +A game with imperfect information. +This example shows how a requirement that a substrategy be a best response in +all subgames is too simplistic for defining SPE in games with imperfect information. +" + +p "" 1 2 "" { "L" "C" "R" } 0 +t "" 1 "" { 1 1 } +p "" 2 1 "" { "U" "D" } 0 +t "" 2 "" { 0 1000 } +t "" 3 "" { 0 0 } +p "" 2 1 0 +t "" 4 "" { 1 0 } +t "" 5 "" { 3 1 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_15__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_15__original_layout.ef new file mode 100644 index 0000000000..50d5d34460 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_15__original_layout.ef @@ -0,0 +1,11 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 xshift -5.01 from 0,1 move L payoffs 1 1 +level 2 node 2 xshift -0.72 from 0,1 move C +level 2 node 3 xshift 5.01 from 0,1 move R +level 4 node 1 xshift -1.43 from 2,2 move U payoffs 0 1000 +level 4 node 2 xshift 1.43 from 2,2 move D payoffs 0 0 +level 4 node 3 xshift -1.43 from 2,3 move U payoffs 1 0 +level 4 node 4 xshift 1.43 from 2,3 move D payoffs 3 1 +iset 2,2 2,3 player 2 diff --git a/catalog/books/shohamleytonbrown2008/fig5_1__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_1__original_layout.ef new file mode 100644 index 0000000000..492f047541 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_1__original_layout.ef @@ -0,0 +1,12 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 player 2 xshift -4.77 from 0,1 move 2-0 +level 2 node 2 player 2 xshift 0 from 0,1 move 1-1 +level 4 node 1 xshift -1.19 from 2,1 move no payoffs 0 0 +level 4 node 2 xshift 1.19 from 2,1 move yes payoffs 2 0 +level 2 node 3 player 2 xshift 4.77 from 0,1 move 0-2 +level 4 node 3 xshift -1.19 from 2,2 move no payoffs 0 0 +level 4 node 4 xshift 1.19 from 2,2 move yes payoffs 1 1 +level 4 node 5 xshift -1.19 from 2,3 move no payoffs 0 0 +level 4 node 6 xshift 1.19 from 2,3 move yes payoffs 0 2 diff --git a/catalog/books/shohamleytonbrown2008/fig5_2.efg b/catalog/books/shohamleytonbrown2008/fig5_2.efg new file mode 100644 index 0000000000..5ed1bd1893 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_2.efg @@ -0,0 +1,16 @@ +EFG 2 R "Fig 5.2 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.2 from :cite:p:`ShoLeyB08`. +This is an example of a perfect-information game in extensive form. +" + +p "" 1 1 "" { "A" "B" } 0 +p "" 2 2 "" { "C" "D" } 0 +t "" 1 "" { 3 8 } +t "" 2 "" { 8 3 } +p "" 2 3 "" { "E" "F" } 0 +t "" 3 "" { 5 5 } +p "" 1 4 "" { "G" "H" } 0 +t "" 4 "" { 2 10 } +t "" 5 "" { 1 0 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_2__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_2__original_layout.ef new file mode 100644 index 0000000000..1d55ae617a --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_2__original_layout.ef @@ -0,0 +1,11 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 player 2 xshift -3.22 from 0,1 move A +level 2 node 2 player 2 xshift 3.22 from 0,1 move B +level 4 node 1 xshift -1.43 from 2,1 move C payoffs 3 8 +level 4 node 2 xshift 1.43 from 2,1 move D payoffs 8 3 +level 4 node 3 xshift -2.15 from 2,2 move E payoffs 5 5 +level 4 node 4 player 1 xshift 2.15 from 2,2 move F +level 6 node 1 xshift -1.43 from 4,4 move G payoffs 2 10 +level 6 node 2 xshift 1.43 from 4,4 move H payoffs 1 0 diff --git a/catalog/books/shohamleytonbrown2008/fig5_9.efg b/catalog/books/shohamleytonbrown2008/fig5_9.efg new file mode 100644 index 0000000000..861c7c0b70 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_9.efg @@ -0,0 +1,22 @@ +EFG 2 R "Fig 5.9 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 5.9 from :cite:p:`ShoLeyB08`. +This is the centipede game. In this game two players alternate in making decisions, at each +turn choosing between going down and ending the game or going across and +continuing it. +This example is used to explain the criticisms of backward induction for finding subgame-perfect equilibrium. +Note that centipede is also a parametrized game, with the parameter being the number of rounds. +" + +p "" 1 1 "" { "A" "D" } 0 +t "" 1 "" { 1 0 } +p "" 2 2 "" { "A" "D" } 0 +t "" 2 "" { 0 2 } +p "" 1 3 "" { "A" "D" } 0 +t "" 3 "" { 3 1 } +p "" 2 4 "" { "A" "D" } 0 +t "" 4 "" { 2 4 } +p "" 1 5 "" { "A" "D" } 0 +t "" 5 "" { 4 3 } +t "" 6 "" { 3 5 } diff --git a/catalog/books/shohamleytonbrown2008/fig5_9__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig5_9__original_layout.ef new file mode 100644 index 0000000000..9f477bc52a --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig5_9__original_layout.ef @@ -0,0 +1,13 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 xshift -2.31 from 0,1 move A payoffs 1 0 +level 2 node 2 player 2 xshift 2.31 from 0,1 move D +level 4 node 1 xshift -2.24 from 2,2 move A payoffs 0 2 +level 4 node 2 player 1 xshift 2.24 from 2,2 move D +level 6 node 1 xshift -2.09 from 4,2 move A payoffs 3 1 +level 6 node 2 player 2 xshift 2.09 from 4,2 move D +level 8 node 1 xshift -1.79 from 6,2 move A payoffs 2 4 +level 8 node 2 player 1 xshift 1.79 from 6,2 move D +level 10 node 1 xshift -1.19 from 8,2 move A payoffs 4 3 +level 10 node 2 xshift 1.19 from 8,2 move D payoffs 3 5 diff --git a/catalog/books/shohamleytonbrown2008/fig6_2.efg b/catalog/books/shohamleytonbrown2008/fig6_2.efg new file mode 100644 index 0000000000..6a543e3615 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig6_2.efg @@ -0,0 +1,38 @@ +EFG 2 R "Fig 6.2 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 6.2 from :cite:p:`ShoLeyB08`. +This is a repeated game, where Prisoner's Dilemma is played twice. +" + +p "" 1 6 "" { "C" "D" } 0 +p "" 2 1 "" { "c" "d" } 0 +p "" 1 7 "" { "C" "D" } 0 +p "" 2 4 "" { "c" "d" } 0 +t "" 1 "" { -2 -2 } +t "" 2 "" { -5 -1 } +p "" 2 4 0 +t "" 3 "" { -1 -5 } +t "" 4 "" { -4 -4 } +p "" 1 8 "" { "C" "D" } 0 +p "" 2 5 "" { "c" "d" } 0 +t "" 5 "" { -5 -1 } +t "" 6 "" { -8 0 } +p "" 2 5 0 +t "" 7 "" { -4 -4 } +t "" 8 "" { -7 -3 } +p "" 2 1 0 +p "" 1 9 "" { "C" "D" } 0 +p "" 2 2 "" { "c" "d" } 0 +t "" 9 "" { -1 -5 } +t "" 10 "" { -4 -4 } +p "" 2 2 0 +t "" 11 "" { 0 -8 } +t "" 12 "" { -3 -7 } +p "" 1 10 "" { "C" "D" } 0 +p "" 2 3 "" { "c" "d" } 0 +t "" 13 "" { -4 -4 } +t "" 14 "" { -7 -3 } +p "" 2 3 0 +t "" 15 "" { -3 -7 } +t "" 16 "" { -6 -6 } diff --git a/catalog/books/shohamleytonbrown2008/fig6_2__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig6_2__original_layout.ef new file mode 100644 index 0000000000..563e3b0f07 --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig6_2__original_layout.ef @@ -0,0 +1,38 @@ +player 1 name 1 +player 2 name 2 +level 0 node 1 player 1 +level 2 node 1 xshift -6.37 from 0,1 move C +level 2 node 2 xshift 6.37 from 0,1 move D +level 4 node 1 player 1 xshift -3.19 from 2,1 move c +level 4 node 2 player 1 xshift 3.19 from 2,1 move d +level 4 node 3 player 1 xshift -3.19 from 2,2 move c +level 4 node 4 player 1 xshift 3.19 from 2,2 move d +level 6 node 1 xshift -1.59 from 4,1 move C +level 6 node 2 xshift 1.59 from 4,1 move D +level 6 node 3 xshift -1.59 from 4,2 move C +level 6 node 4 xshift 1.59 from 4,2 move D +level 6 node 5 xshift -1.59 from 4,3 move C +level 6 node 6 xshift 1.59 from 4,3 move D +level 6 node 7 xshift -1.59 from 4,4 move C +level 6 node 8 xshift 1.59 from 4,4 move D +level 8 node 1 xshift -0.8 from 6,1 move c payoffs -2 -2 +level 8 node 2 xshift 0.8 from 6,1 move d payoffs -5 -1 +level 8 node 3 xshift -0.8 from 6,2 move c payoffs -1 -5 +level 8 node 4 xshift 0.8 from 6,2 move d payoffs -4 -4 +level 8 node 5 xshift -0.8 from 6,3 move c payoffs -5 -1 +level 8 node 6 xshift 0.8 from 6,3 move d payoffs -8 0 +level 8 node 7 xshift -0.8 from 6,4 move c payoffs -4 -4 +level 8 node 8 xshift 0.8 from 6,4 move d payoffs -7 -3 +level 8 node 9 xshift -0.8 from 6,5 move c payoffs -1 -5 +level 8 node 10 xshift 0.8 from 6,5 move d payoffs -4 -4 +level 8 node 11 xshift -0.8 from 6,6 move c payoffs 0 -8 +level 8 node 12 xshift 0.8 from 6,6 move d payoffs -3 -7 +level 8 node 13 xshift -0.8 from 6,7 move c payoffs -4 -4 +level 8 node 14 xshift 0.8 from 6,7 move d payoffs -7 -3 +level 8 node 15 xshift -0.8 from 6,8 move c payoffs -3 -7 +level 8 node 16 xshift 0.8 from 6,8 move d payoffs -6 -6 +iset 2,1 2,2 player 2 +iset 6,5 6,6 player 2 +iset 6,7 6,8 player 2 +iset 6,1 6,2 player 2 +iset 6,3 6,4 player 2 diff --git a/catalog/books/shohamleytonbrown2008/fig6_8.efg b/catalog/books/shohamleytonbrown2008/fig6_8.efg new file mode 100644 index 0000000000..08e0e3cbac --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig6_8.efg @@ -0,0 +1,36 @@ +EFG 2 R "Fig 6.8 from Shoham and Leyton-Brown (2008)" { "1" "2" } + +" +Figure 6.8 from :cite:p:`ShoLeyB08`. +This is a Bayesian game, represented in EFG with Nature deciding the types. +" + +c "" 5 "" { "MP" 1/4 "PD" 1/4 "Coord" 1/4 "BoS" 1/4 } 0 +p "" 1 1 "" { "U" "D" } 0 +p "" 2 3 "" { "L" "R" } 0 +t "" 1 "" { 2 0 } +t "" 2 "" { 0 2 } +p "" 2 3 0 +t "" 3 "" { 0 2 } +t "" 4 "" { 2 0 } +p "" 1 1 0 +p "" 2 4 "" { "L" "R" } 0 +t "" 5 "" { 2 2 } +t "" 6 "" { 0 3 } +p "" 2 4 0 +t "" 7 "" { 3 0 } +t "" 8 "" { 1 1 } +p "" 1 2 "" { "U" "D" } 0 +p "" 2 3 0 +t "" 9 "" { 2 2 } +t "" 10 "" { 0 0 } +p "" 2 3 0 +t "" 11 "" { 0 0 } +t "" 12 "" { 1 1 } +p "" 1 2 0 +p "" 2 4 0 +t "" 13 "" { 2 1 } +t "" 14 "" { 0 0 } +p "" 2 4 0 +t "" 15 "" { 0 0 } +t "" 16 "" { 1 2 } diff --git a/catalog/books/shohamleytonbrown2008/fig6_8__original_layout.ef b/catalog/books/shohamleytonbrown2008/fig6_8__original_layout.ef new file mode 100644 index 0000000000..a01dfbf0fa --- /dev/null +++ b/catalog/books/shohamleytonbrown2008/fig6_8__original_layout.ef @@ -0,0 +1,92 @@ +% Four games with uniform chance and non-overlapping information sets. +% Nature chooses among MP, PD, Coord, and BoS with probability 1/4 each. +% +% The Player 2 information sets are separated vertically: +% - MP and Coord nodes are at level 5. +% - PD and BoS nodes are at level 11. +% +% The additional spacing prevents the U/D labels and the terminal +% payoffs of the upper subtrees from overlapping either information set. + +player 0 name Nature +player 1 name 1 +player 2 name 2 + +% Nature root +level 0 node 1 player 0 + +% Nature moves +% MP and PD belong to Player 1's first information set. +level 2 node 1 xshift -7.5 from 0,1 move MP~(1/4) +level 2 node 2 xshift -2.5 from 0,1 move PD~(1/4) + +% Coord and BoS belong to Player 1's second information set. +level 3 node 3 xshift 2.5 from 0,1 move Coord~(1/4) +level 3 node 4 xshift 7.5 from 0,1 move BoS~(1/4) + +% Player 1 moves U/D + +% Matching Pennies: +% These nodes belong to Player 2 information set 1. +level 5 node 1 xshift -1 from 2,1 move U +level 5 node 2 xshift 1 from 2,1 move D + +% Prisoner's Dilemma: +% These nodes belong to Player 2 information set 2. +level 11 node 3 xshift -1 from 2,2 move U +level 11 node 4 xshift 1 from 2,2 move D + +% Coordination: +% These nodes belong to Player 2 information set 1. +level 5 node 5 xshift -1 from 3,3 move U +level 5 node 6 xshift 1 from 3,3 move D + +% Battle of the Sexes: +% These nodes belong to Player 2 information set 2. +level 11 node 7 xshift -1 from 3,4 move U +level 11 node 8 xshift 1 from 3,4 move D + +% Player 2 moves L/R and terminal payoffs + +% Matching Pennies +% The terminal level is sufficiently above the second information set. +level 8 node 1 xshift -0.55 from 5,1 move L payoffs 2 0 +level 8 node 2 xshift 0.55 from 5,1 move R payoffs 0 2 +level 8 node 3 xshift -0.55 from 5,2 move L payoffs 0 2 +level 8 node 4 xshift 0.55 from 5,2 move R payoffs 2 0 + +% Prisoner's Dilemma +level 14 node 5 xshift -0.55 from 11,3 move L payoffs 2 2 +level 14 node 6 xshift 0.55 from 11,3 move R payoffs 0 3 +level 14 node 7 xshift -0.55 from 11,4 move L payoffs 3 0 +level 14 node 8 xshift 0.55 from 11,4 move R payoffs 1 1 + +% Coordination +% These payoffs are placed at level 8, leaving three levels before +% the lower Player 2 information set at level 11. +level 8 node 9 xshift -0.55 from 5,5 move L payoffs 2 2 +level 8 node 10 xshift 0.55 from 5,5 move R payoffs 0 0 +level 8 node 11 xshift -0.55 from 5,6 move L payoffs 0 0 +level 8 node 12 xshift 0.55 from 5,6 move R payoffs 1 1 + +% Battle of the Sexes +level 14 node 13 xshift -0.55 from 11,7 move L payoffs 2 1 +level 14 node 14 xshift 0.55 from 11,7 move R payoffs 0 0 +level 14 node 15 xshift -0.55 from 11,8 move L payoffs 0 0 +level 14 node 16 xshift 0.55 from 11,8 move R payoffs 1 2 + +% Player 1 information sets + +% Player 1 cannot distinguish MP from PD. +iset 2,1 2,2 player 1 + +% Player 1 cannot distinguish Coordination from Battle of the Sexes. +iset 3,3 3,4 player 1 + +% Player 2 information sets + +% Player 2 cannot distinguish MP from Coordination. +iset 5,1 5,2 5,5 5,6 player 2 + +% Player 2 cannot distinguish PD from Battle of the Sexes. +iset 11,3 11,4 11,7 11,8 player 2 diff --git a/catalog/journals/mor/vonstengelforges2008/fig1__Original_Layout.ef b/catalog/journals/mor/vonstengelforges2008/fig1__Original_Layout.ef new file mode 100644 index 0000000000..9896620e88 --- /dev/null +++ b/catalog/journals/mor/vonstengelforges2008/fig1__Original_Layout.ef @@ -0,0 +1,57 @@ +% Extensive-form game transcribed from the provided figure. +% DrawTree .ef format. +% Layout is chosen to preserve the original geometry of the image: +% chance at the top, two Player 1 singleton information sets, +% crossed middle branches, and two horizontal Player 2 information sets. +% +% Player 0: Chance +% Player 1: 1 +% Player 2: 2 +% +% Chance chooses G or B with probability 1/2 each. +% Player 1 observes the chance outcome and chooses X or Y, with labels +% indexed by the realized chance outcome: X_G, Y_G, X_B, Y_B. +% Player 2 observes only whether the signal is X or Y, not the chance outcome. + +player 0 name Chance +player 1 name 1 +player 2 name 2 + +% Root chance node. +level 0 node n1 player 0 + +% Chance branches, arranged as in the figure. +level 2 node n2 xshift -2.8 from n1 move \frac{1}{2} player 1 +level 2 node n3 xshift 2.8 from n1 move \frac{1}{2} player 1 + +% Player 1 branches. +% Left Player 1 node: chance outcome G. +level 4 node n4 xshift -2.5 yshift -0.9 from n2 move X_G +level 4 node n5 xshift 3.9 yshift -0.9 from n2 move Y_G + +% Right Player 1 node: chance outcome B. +% The X_B and Y_G branches cross, matching the original layout. +level 4 node n6 xshift -3.9 yshift -0.9 from n3 move X_B +level 4 node n7 xshift 2.5 yshift -0.9 from n3 move Y_B + +% Player 2 information sets. +% X-signal information set: histories (G,X_G) and (B,X_B). +iset n4 n6 player 2 + +% Y-signal information set: histories (G,Y_G) and (B,Y_B). +iset n5 n7 player 2 + +% Player 2 actions and terminal payoffs, listed left-to-right as in the image. +% At the X-signal information set. +level 6 node n8 xshift -0.45 from n4 move l_X payoffs 4 10 +level 6 node n9 xshift 0.45 from n4 move r_X payoffs 0 6 + +level 6 node n10 xshift -0.45 from n6 move l_X payoffs 6 0 +level 6 node n11 xshift 0.45 from n6 move r_X payoffs 0 6 + +% At the Y-signal information set. +level 6 node n12 xshift -0.45 from n5 move l_Y payoffs 4 10 +level 6 node n13 xshift 0.45 from n5 move r_Y payoffs 0 6 + +level 6 node n14 xshift -0.45 from n7 move l_Y payoffs 6 0 +level 6 node n15 xshift 0.45 from n7 move r_Y payoffs 0 6 diff --git a/doc/algorithms.rst b/doc/algorithms.rst index 0f4b332601..260ea688db 100644 --- a/doc/algorithms.rst +++ b/doc/algorithms.rst @@ -15,7 +15,7 @@ Algorithm Description :ref:`lp` Compute equilibria in a two-player constant-sum game via linear programming :py:func:`pygambit.nash.lp_solve` :ref:`gambit-lp ` :ref:`lcp` Compute equilibria in a two-player game via linear complementarity :py:func:`pygambit.nash.lcp_solve` :ref:`gambit-lcp ` :ref:`liap` Compute equilibria using function minimization :py:func:`pygambit.nash.liap_solve` :ref:`gambit-liap ` -:ref:`logit` Compute quantal response equilibria :py:func:`pygambit.nash.logit_solve` :ref:`gambit-logit ` +:ref:`logit` Trace logit QRE and approximate a Nash equilibrium at high precision :py:func:`pygambit.nash.logit_solve` :ref:`gambit-logit ` :ref:`simpdiv` Compute equilibria via simplicial subdivision :py:func:`pygambit.nash.simpdiv_solve` :ref:`gambit-simpdiv ` :ref:`ipa` Compute equilibria using iterated polymatrix approximation :py:func:`pygambit.nash.ipa_solve` :ref:`gambit-ipa ` :ref:`gnm` Compute equilibria using a global Newton method :py:func:`pygambit.nash.gnm_solve` :ref:`gambit-gnm ` @@ -141,6 +141,10 @@ logit Computes the principal branch of the (logit) quantal response correspondence. +Following this branch toward large values of the precision parameter +provides an approximation to a Nash equilibrium. The graphical +interface uses this limiting calculation when it selects ``logit`` as +the recommended method for computing one Nash equilibrium. The method is based on the procedure described in Turocy :cite:p:`Tur05` for strategic games and Turocy :cite:p:`Tur10` for extensive games. diff --git a/doc/gui.nash.rst b/doc/gui.nash.rst index 1891dad4ce..b9b9a200f6 100644 --- a/doc/gui.nash.rst +++ b/doc/gui.nash.rst @@ -38,22 +38,62 @@ are to be found. Some algorithms for computing equilibria are adapted to finding a single equilibrium, while others attempt to compute the whole equilibrium set. The first drop-down in the dialog specifies how many equilibria to compute. In this drop-down there are options for -:guilabel:`as many equilibria as possible` and, for two-player games, -:guilabel:`all equilibria`. For some games, there exist algorithms which will +:guilabel:`Compute one Nash equilibrium`, :guilabel:`Compute some Nash equilibria`, +and, for two-player games, :guilabel:`Compute all Nash equilibria`. +For some games, there exist algorithms which will compute many equilibria (relatively) efficiently, but are not guaranteed to find all equilibria. -To simplify this process of choosing the method to compute equilibria -in the second drop-down, Gambit provides for any game "recommended" -methods for computing one, some, and all Nash equilibria, -respectively. These methods are selected based on experience as to the -efficiency and reliability of the methods, and should generally work -well on most games. For more control over the process, the user can -select from the second drop-down in the dialog one of the appropriate -methods for computing equilibria. This list only shows the methods -which are appropriate for the game, given the selection of how many -equilibria to compute. More details on these methods are contained -in :ref:`command-line`. +To simplify this process of choosing the method to compute equilibria, +the second drop-down offers :guilabel:`Gambit's recommended method`. +The recommendation is determined by the requested number of equilibria +and the structure of the game as follows. + +======================= ============================================= ================== +Requested output Game Method +======================= ============================================= ================== +One equilibrium Two players and constant sum :ref:`lp` +One equilibrium Any other supported game :ref:`logit` +Some equilibria Two players :ref:`lcp` +Some equilibria Three or more players :ref:`simpdiv` +All equilibria Two players :ref:`enummixed` +======================= ============================================= ================== + +The :guilabel:`all equilibria` choice is therefore offered only for +two-player games. The recommendation selects an algorithm; it does not +infer properties such as uniqueness. In particular, "some" means that +the selected method searches for multiple equilibria where possible, not +that a particular number of distinct equilibria is guaranteed. + +For more control, the user can select one of the other methods shown in +the second drop-down. The list is filtered using the requested output +category and the methods' applicability. Methods which only operate on +strategic games automatically select and lock the strategic +representation. Otherwise an extensive game may be solved using either +its behavior representation or its reduced strategic representation. +See :ref:`algorithms` for descriptions and limitations of the methods. + +The current dialog uses the following solver parameters. They are +recorded with each computation so that the interface can expose them for +editing in a future revision. + +================ ============================================================================ +Method Parameters used by the graphical interface +================ ============================================================================ +``enumpure`` Enumerate all pure-strategy equilibria. +``enummixed`` Enumerate all mixed-strategy extreme equilibria. +``enumpoly`` Stop after the first equilibrium when "one" is requested; otherwise search all + supports; maximum regret ``1e-4``. +``lp`` No method-specific parameters. +``lcp`` Search all accessible equilibria; unlimited recursion depth. +``liap`` 10 random starting points; 1,000 iterations per point; maximum regret ``1e-4``. +``logit`` Maximum regret ``1e-8``; initial step ``0.03``; maximum acceleration ``1.1``. +``simpdiv`` One random starting point when "one" is requested, otherwise 20; denominator + 100; grid resize factor 2; maximum regret ``1/10000000``. +``ipa`` One random perturbation. +``gnm`` One random perturbation; ending lambda ``-10``; 100 steps per support cell; + local Newton refinement every 3 steps, with at most 10 iterations. +================ ============================================================================ .. image:: screens/computing.* :width: 33% @@ -61,11 +101,12 @@ in :ref:`command-line`. :align: right :target: _images/computing.png -Finally, for extensive games, there is an option of whether to use the -extensive or strategic game for computation. In general, computation +For extensive games, there is an option of whether to use the +extensive or strategic game for computation when the selected method +supports both. In general, computation using the extensive game is preferred, since it is often a significantly more compact representation of the strategic -characeteristics of the game than the reduced strategic game is. +characteristics of the game than the reduced strategic game is. For even moderate sized games, computation of equilibrium can be a time-intensive process. Gambit runs all computations in the diff --git a/doc/references.bib b/doc/references.bib index 36ae6110e6..728be714ca 100644 --- a/doc/references.bib +++ b/doc/references.bib @@ -430,5 +430,6 @@ @book{ShoLeyB08 title={Multiagent systems: Algorithmic, game-theoretic, and logical foundations}, author={Shoham, Yoav and Leyton-Brown, Kevin}, year={2008}, - publisher={Cambridge University Press} + publisher={Cambridge University Press}, + category = {textbooks} } diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index 1f4f6fd0ee..bba579e9f6 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -4,7 +4,30 @@ "cell_type": "markdown", "id": "96019084", "metadata": {}, - "source": "# 2) Extensive-form games\n\nIn the first tutorial, we used Gambit to set up the Prisoner's Dilemma, an example of a normal (strategic) form game.\n\nGambit can also be used to set up extensive-form games; the game is represented as a tree, where each node represents a decision point for a player, and the branches represent the possible actions they can take.\n\n**Example: One-shot trust game with binary actions**\n\n[Kreps (1990)](#references) introduced a game commonly referred to as the **trust game**.\nWe will build a one-shot version of this game using Gambit's game transformation operations.\n\nThe game can be defined as follows:\n- There are two players, a **Buyer** and a **Seller**.\n- The Buyer moves first and has two actions, **Trust** or **Not trust**.\n- If the Buyer chooses **Not trust**, then the game ends, and both players receive payoffs of `0`.\n- If the Buyer chooses **Trust**, then the Seller has a choice with two actions, **Honor** or **Abuse**.\n- If the Seller chooses **Honor**, both players receive payoffs of `1`;\n- If the Seller chooses **Abuse**, the Buyer receives a payoff of `-1` and the Seller receives a payoff of `2`.\n\nIn addition to `pygambit`, this tutorial introduces the `gtdraw` package, which can be used to draw extensive form games in Python.\nIf you're running this tutorial on your local machine, you'll need to install the requirements for [gtdraw](https://www.gambit-project.org/gtdraw/), which include LaTeX, in order to run the `gtdraw` cells.\nAnother option for visualising extensive form games is to install the Gambit GUI and use it to load the EFG file generated at the end of this tutorial." + "source": [ + "# 2) Extensive-form games\n", + "\n", + "In the first tutorial, we used Gambit to set up the Prisoner's Dilemma, an example of a normal (strategic) form game.\n", + "\n", + "Gambit can also be used to set up extensive-form games; the game is represented as a tree, where each node represents a decision point for a player, and the branches represent the possible actions they can take.\n", + "\n", + "**Example: One-shot trust game with binary actions**\n", + "\n", + "[Kreps (1990)](#references) introduced a game commonly referred to as the **trust game**.\n", + "We will build a one-shot version of this game using Gambit's game transformation operations.\n", + "\n", + "The game can be defined as follows:\n", + "- There are two players, a **Buyer** and a **Seller**.\n", + "- The Buyer moves first and has two actions, **Trust** or **Not trust**.\n", + "- If the Buyer chooses **Not trust**, then the game ends, and both players receive payoffs of `0`.\n", + "- If the Buyer chooses **Trust**, then the Seller has a choice with two actions, **Honor** or **Abuse**.\n", + "- If the Seller chooses **Honor**, both players receive payoffs of `1`;\n", + "- If the Seller chooses **Abuse**, the Buyer receives a payoff of `-1` and the Seller receives a payoff of `2`.\n", + "\n", + "In addition to `pygambit`, this tutorial introduces the `gtdraw` package, which can be used to draw extensive form games in Python.\n", + "If you're running this tutorial on your local machine, you'll need to install the requirements for [gtdraw](https://www.gambit-project.org/gtdraw/), which include LaTeX, in order to run the `gtdraw` cells.\n", + "Another option for visualising extensive form games is to install the Gambit GUI and use it to load the EFG file generated at the end of this tutorial." + ] }, { "cell_type": "code", @@ -12,7 +35,11 @@ "id": "5946289b", "metadata": {}, "outputs": [], - "source": "from gtdraw import draw\n\nimport pygambit as gbt" + "source": [ + "from gtdraw import draw\n", + "\n", + "import pygambit as gbt" + ] }, { "cell_type": "markdown", @@ -49,7 +76,9 @@ "id": "3cd94917", "metadata": {}, "outputs": [], - "source": "draw(g)" + "source": [ + "draw(g)" + ] }, { "cell_type": "markdown", @@ -81,7 +110,9 @@ "id": "45638fda-7e25-4c8e-b709-24b05780581b", "metadata": {}, "outputs": [], - "source": "draw(g)" + "source": [ + "draw(g)" + ] }, { "cell_type": "markdown", @@ -111,7 +142,9 @@ "id": "ce41e9fe-cca4-46fb-8e9d-b2c27342e5ef", "metadata": {}, "outputs": [], - "source": "draw(g)" + "source": [ + "draw(g)" + ] }, { "cell_type": "markdown", @@ -134,10 +167,7 @@ "source": [ "g.set_outcome(\n", " g.root.children[\"Trust\"].children[\"Honor\"],\n", - " outcome=g.add_outcome(\n", - " payoffs=[1, 1],\n", - " label=\"Trustworthy\"\n", - " )\n", + " outcome=g.add_outcome(\"Trustworthy\", [1, 1])\n", ")" ] }, @@ -147,7 +177,9 @@ "id": "b3408c55-714e-4a6f-b598-e338839442e4", "metadata": {}, "outputs": [], - "source": "draw(g)" + "source": [ + "draw(g)" + ] }, { "cell_type": "markdown", @@ -166,10 +198,7 @@ "source": [ "g.set_outcome(\n", " g.root.children[\"Trust\"].children[\"Abuse\"],\n", - " outcome=g.add_outcome(\n", - " payoffs=[-1, 2],\n", - " label=\"Untrustworthy\"\n", - " )\n", + " outcome=g.add_outcome(\"Untrustworthy\", [-1, 2])\n", ")" ] }, @@ -179,7 +208,9 @@ "id": "09bedb3a-aac7-46e6-ae93-c47932c746d4", "metadata": {}, "outputs": [], - "source": "draw(g)" + "source": [ + "draw(g)" + ] }, { "cell_type": "markdown", @@ -198,10 +229,7 @@ "source": [ "g.set_outcome(\n", " g.root.children[\"Not trust\"],\n", - " g.add_outcome(\n", - " payoffs=[0, 0],\n", - " label=\"Opt-out\"\n", - " )\n", + " g.add_outcome(\"Opt-out\", [0, 0])\n", ")" ] }, @@ -211,7 +239,9 @@ "id": "cba0e562-2989-4dae-a0f0-b121635ba032", "metadata": {}, "outputs": [], - "source": "draw(g)" + "source": [ + "draw(g)" + ] }, { "cell_type": "markdown", @@ -258,7 +288,10 @@ "id": "56899a29-cc53-48db-9eb4-ed2295517400", "metadata": {}, "outputs": [], - "source": "g = gbt.catalog.load(\"journals/ijgt/selten1975/fig2\")\ndraw(g)" + "source": [ + "g = gbt.catalog.load(\"journals/ijgt/selten1975/fig2\")\n", + "draw(g)" + ] }, { "cell_type": "markdown", diff --git a/doc/tutorials/03_stripped_down_poker.ipynb b/doc/tutorials/03_stripped_down_poker.ipynb index 5dc52da3d7..7f47f183f4 100644 --- a/doc/tutorials/03_stripped_down_poker.ipynb +++ b/doc/tutorials/03_stripped_down_poker.ipynb @@ -4,7 +4,38 @@ "cell_type": "markdown", "id": "98eb65d8", "metadata": {}, - "source": "# 3) Stripped-down poker\n\nIn this tutorial, we'll create an extensive-form representation of a one-card poker game from [Reiley et al (2008)](#references), a classroom game under the name \"stripped-down poker\".\nThis is perhaps the simplest interesting game with imperfect information.\n\nWe'll use \"stripped-down poker\" to demonstrate and explain the following with Gambit:\n\n1. Setting up an extensive-form game with imperfect information using [information sets](#information-sets)\n2. [Computing and interpreting Nash equilibria](#computing-and-interpreting-nash-equilibria) and understanding mixed behaviour and mixed strategy profiles\n3. [Acceptance criteria for Nash equilibria](#acceptance-criteria-for-nash-equilibria)\n\nIn our version of the game, there are two players, **Alice** and **Bob**, and a deck of cards, with equal numbers of **King** and **Queen** cards.\n\n- The game begins with each player putting \\$1 in the pot.\n - A card is dealt at random to Alice.\n - Alice observes her card.\n - Bob does not observe the card.\n- Alice then chooses either to **Bet** or to **Fold**.\n - If she chooses to Fold, Bob wins the pot and the game ends.\n - If she chooses to Bet, she adds another \\$1 to the pot.\n- Bob then chooses either to **Call** or **Fold**.\n - If he chooses to Fold, Alice wins the pot and the game ends.\n - If he chooses to Call, he adds another $1 to the pot.\n- There is then a showdown, in which Alice reveals her card.\n - If she has a King, then she wins the pot.\n - If she has a Queen, then Bob wins the pot.\n\nIn addition to `pygambit`, this tutorial uses the `gtdraw` package, which can be used to draw extensive form games in Python.\nIf you're running this tutorial on your local machine, you'll need to install the requirements for [gtdraw](https://www.gambit-project.org/gtdraw/), which include LaTeX, in order to run the `gtdraw` cells.\nAnother option for visualising extensive form games is to install the Gambit GUI and use it to load a saved EFG file." + "source": [ + "# 3) Stripped-down poker\n", + "\n", + "In this tutorial, we'll create an extensive-form representation of a one-card poker game from [Reiley et al (2008)](#references), a classroom game under the name \"stripped-down poker\".\n", + "This is perhaps the simplest interesting game with imperfect information.\n", + "\n", + "We'll use \"stripped-down poker\" to demonstrate and explain the following with Gambit:\n", + "\n", + "1. Setting up an extensive-form game with imperfect information using [information sets](#information-sets)\n", + "2. [Computing and interpreting Nash equilibria](#computing-and-interpreting-nash-equilibria) and understanding mixed behaviour and mixed strategy profiles\n", + "3. [Acceptance criteria for Nash equilibria](#acceptance-criteria-for-nash-equilibria)\n", + "\n", + "In our version of the game, there are two players, **Alice** and **Bob**, and a deck of cards, with equal numbers of **King** and **Queen** cards.\n", + "\n", + "- The game begins with each player putting \\$1 in the pot.\n", + " - A card is dealt at random to Alice.\n", + " - Alice observes her card.\n", + " - Bob does not observe the card.\n", + "- Alice then chooses either to **Bet** or to **Fold**.\n", + " - If she chooses to Fold, Bob wins the pot and the game ends.\n", + " - If she chooses to Bet, she adds another \\$1 to the pot.\n", + "- Bob then chooses either to **Call** or **Fold**.\n", + " - If he chooses to Fold, Alice wins the pot and the game ends.\n", + " - If he chooses to Call, he adds another $1 to the pot.\n", + "- There is then a showdown, in which Alice reveals her card.\n", + " - If she has a King, then she wins the pot.\n", + " - If she has a Queen, then Bob wins the pot.\n", + "\n", + "In addition to `pygambit`, this tutorial uses the `gtdraw` package, which can be used to draw extensive form games in Python.\n", + "If you're running this tutorial on your local machine, you'll need to install the requirements for [gtdraw](https://www.gambit-project.org/gtdraw/), which include LaTeX, in order to run the `gtdraw` cells.\n", + "Another option for visualising extensive form games is to install the Gambit GUI and use it to load a saved EFG file." + ] }, { "cell_type": "code", @@ -12,7 +43,11 @@ "id": "69cbfe81", "metadata": {}, "outputs": [], - "source": "from gtdraw import draw\n\nimport pygambit as gbt" + "source": [ + "from gtdraw import draw\n", + "\n", + "import pygambit as gbt" + ] }, { "cell_type": "markdown", @@ -89,7 +124,9 @@ "id": "867cb1d8-7a5d-45d1-9349-9bbc2a4e2344", "metadata": {}, "outputs": [], - "source": "draw(g, color_scheme=\"gambit\")" + "source": [ + "draw(g, color_scheme=\"gambit\")" + ] }, { "cell_type": "markdown", @@ -126,7 +163,9 @@ "id": "0c522c2d-992e-48b6-a1f8-0696d33cdbe0", "metadata": {}, "outputs": [], - "source": "draw(g, color_scheme=\"gambit\")" + "source": [ + "draw(g, color_scheme=\"gambit\")" + ] }, { "cell_type": "markdown", @@ -166,7 +205,9 @@ "id": "e85b3346-2fea-4a73-aa72-9efb436c68c1", "metadata": {}, "outputs": [], - "source": "draw(g, color_scheme=\"gambit\")" + "source": [ + "draw(g, color_scheme=\"gambit\")" + ] }, { "cell_type": "markdown", @@ -188,10 +229,10 @@ "metadata": {}, "outputs": [], "source": [ - "win_big = g.add_outcome([2, -2], label=\"Win Big\")\n", - "win = g.add_outcome([1, -1], label=\"Win\")\n", - "lose_big = g.add_outcome([-2, 2], label=\"Lose Big\")\n", - "lose = g.add_outcome([-1, 1], label=\"Lose\")" + "win_big = g.add_outcome(\"Win Big\", [2, -2])\n", + "win = g.add_outcome(\"Win\", [1, -1])\n", + "lose_big = g.add_outcome(\"Lose Big\", [-2, 2])\n", + "lose = g.add_outcome(\"Lose\", [-1, 1])" ] }, { @@ -230,7 +271,9 @@ "id": "fdee7b53-7820-44df-9d17-d15d0b9667aa", "metadata": {}, "outputs": [], - "source": "draw(g, color_scheme=\"gambit\")" + "source": [ + "draw(g, color_scheme=\"gambit\")" + ] }, { "cell_type": "markdown", diff --git a/doc/tutorials/interoperability_tutorials/openspiel.ipynb b/doc/tutorials/interoperability_tutorials/openspiel.ipynb index 21fbf02568..de36447bcc 100644 --- a/doc/tutorials/interoperability_tutorials/openspiel.ipynb +++ b/doc/tutorials/interoperability_tutorials/openspiel.ipynb @@ -468,7 +468,17 @@ "id": "b913fc7a", "metadata": {}, "outputs": [], - "source": "from gtdraw import draw\n\ndraw(\n gbt_hanabi_game,\n color_scheme=\"gambit\",\n edge_thickness=2,\n action_label_position=0.8,\n shared_terminal_depth=True\n)" + "source": [ + "from gtdraw import draw\n", + "\n", + "draw(\n", + " gbt_hanabi_game,\n", + " color_scheme=\"gambit\",\n", + " edge_thickness=2,\n", + " action_label_position=0.8,\n", + " shared_terminal_depth=True\n", + ")" + ] }, { "cell_type": "markdown", @@ -697,10 +707,10 @@ " actions=[\"Call\", \"Fold\"]\n", ")\n", "\n", - "win_big = gbt_one_card_poker.add_outcome([2, -2], label=\"Win Big\")\n", - "win = gbt_one_card_poker.add_outcome([1, -1], label=\"Win\")\n", - "lose_big = gbt_one_card_poker.add_outcome([-2, 2], label=\"Lose Big\")\n", - "lose = gbt_one_card_poker.add_outcome([-1, 1], label=\"Lose\")\n", + "win_big = gbt_one_card_poker.add_outcome(\"Win Big\", [2, -2])\n", + "win = gbt_one_card_poker.add_outcome(\"Win\", [1, -1])\n", + "lose_big = gbt_one_card_poker.add_outcome(\"Lose Big\", [-2, 2])\n", + "lose = gbt_one_card_poker.add_outcome(\"Lose\", [-1, 1])\n", "\n", "# Alice folds, Bob wins small\n", "gbt_one_card_poker.set_outcome(\n", @@ -741,7 +751,9 @@ "id": "ed920d33-b7c6-4cc1-b055-7244a5bf42d8", "metadata": {}, "outputs": [], - "source": "draw(gbt_one_card_poker, color_scheme=\"gambit\")" + "source": [ + "draw(gbt_one_card_poker, color_scheme=\"gambit\")" + ] }, { "cell_type": "markdown", @@ -833,7 +845,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.13" + "version": "3.13.5" } }, "nbformat": 4, diff --git a/pyproject.toml b/pyproject.toml index 1b36d50efa..e2e4e55f26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,6 +102,7 @@ markers = [ "nash_enumpure_strategy: tests of enumpure_solve in pure strategies", "nash_enumpure_agent: tests of enumpure_solve in pure behaviors", "nash_enummixed_strategy: tests of enummixed_solve in mixed strategies", + "nash_enumpoly_strategy: tests of enumpoly_solve in mixed strategies", "nash_enumpoly_behavior: tests of enumpoly_solve in mixed behaviors", "nash_lcp_strategy: tests of lcp_solve in mixed strategies", "nash_lcp_behavior: tests of lcp_solve in mixed behaviors", diff --git a/src/core/tinyxml.cc b/src/core/tinyxml.cc deleted file mode 100644 index 2f4e58d5f1..0000000000 --- a/src/core/tinyxml.cc +++ /dev/null @@ -1,1559 +0,0 @@ -/* -www.sourceforge.net/projects/tinyxml -Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#include "tinyxml.h" - -#ifdef TIXML_USE_STL -#include -#endif - -bool TiXmlBase::condenseWhiteSpace = true; - -void TiXmlBase::PutString(const TIXML_STRING &str, TIXML_OSTREAM *stream) -{ - TIXML_STRING buffer; - PutString(str, &buffer); - (*stream) << buffer; -} - -void TiXmlBase::PutString(const TIXML_STRING &str, TIXML_STRING *outString) -{ - int i = 0; - - while (i < (int)str.length()) { - auto c = (unsigned char)str[i]; - - if (c == '&' && i < ((int)str.length() - 2) && str[i + 1] == '#' && str[i + 2] == 'x') { - // Hexadecimal character reference. - // Pass through unchanged. - // © -- copyright symbol, for example. - // - // The -1 is a bug fix from Rob Laveaux. It keeps - // an overflow from happening if there is no ';'. - // There are actually 2 ways to exit this loop - - // while fails (error case) and break (semicolon found). - // However, there is no mechanism (currently) for - // this function to return an error. - while (i < (int)str.length() - 1) { - outString->append(str.c_str() + i, 1); - ++i; - if (str[i] == ';') { - break; - } - } - } - else if (c == '&') { - outString->append(entity[0].str, entity[0].strLength); - ++i; - } - else if (c == '<') { - outString->append(entity[1].str, entity[1].strLength); - ++i; - } - else if (c == '>') { - outString->append(entity[2].str, entity[2].strLength); - ++i; - } - else if (c == '\"') { - outString->append(entity[3].str, entity[3].strLength); - ++i; - } - else if (c == '\'') { - outString->append(entity[4].str, entity[4].strLength); - ++i; - } - else if (c < 32) { - // Easy pass at non-alpha/numeric/symbol - // Below 32 is symbolic. - char buf[32]; - -#if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF(buf, sizeof(buf), "&#x%02X;", (unsigned)(c & 0xff)); -#else - sprintf(buf, "&#x%02X;", (unsigned)(c & 0xff)); -#endif - - //*ME: warning C4267: convert 'size_t' to 'int' - //*ME: Int-Cast to make compiler happy ... - outString->append(buf, (int)strlen(buf)); - ++i; - } - else { - // char realc = (char) c; - // outString->append( &realc, 1 ); - *outString += (char)c; // somewhat more efficient function call. - ++i; - } - } -} - -// <-- Strange class for a bug fix. Search for STL_STRING_BUG -TiXmlBase::StringToBuffer::StringToBuffer(const TIXML_STRING &str) - : buffer(new char[str.length() + 1]) -{ - if (buffer) { - strcpy(buffer, str.c_str()); - } -} - -TiXmlBase::StringToBuffer::~StringToBuffer() { delete[] buffer; } -// End strange bug fix. --> - -TiXmlNode::~TiXmlNode() -{ - const TiXmlNode *node = firstChild; - const TiXmlNode *temp = nullptr; - - while (node) { - temp = node; - node = node->next; - delete temp; - } -} - -void TiXmlNode::CopyTo(TiXmlNode *target) const -{ - target->SetValue(value.c_str()); - target->userData = userData; -} - -void TiXmlNode::Clear() -{ - const TiXmlNode *node = firstChild; - const TiXmlNode *temp = nullptr; - - while (node) { - temp = node; - node = node->next; - delete temp; - } - - firstChild = nullptr; - lastChild = nullptr; -} - -TiXmlNode *TiXmlNode::LinkEndChild(TiXmlNode *node) -{ - node->parent = this; - - node->prev = lastChild; - node->next = nullptr; - - if (lastChild) { - lastChild->next = node; - } - else { - firstChild = node; // it was an empty list. - } - - lastChild = node; - return node; -} - -TiXmlNode *TiXmlNode::InsertEndChild(const TiXmlNode &addThis) -{ - TiXmlNode *node = addThis.Clone(); - if (!node) { - return nullptr; - } - - return LinkEndChild(node); -} - -TiXmlNode *TiXmlNode::InsertBeforeChild(TiXmlNode *beforeThis, const TiXmlNode &addThis) -{ - if (!beforeThis || beforeThis->parent != this) { - return nullptr; - } - - TiXmlNode *node = addThis.Clone(); - if (!node) { - return nullptr; - } - node->parent = this; - - node->next = beforeThis; - node->prev = beforeThis->prev; - if (beforeThis->prev) { - beforeThis->prev->next = node; - } - else { - assert(firstChild == beforeThis); - firstChild = node; - } - beforeThis->prev = node; - return node; -} - -TiXmlNode *TiXmlNode::InsertAfterChild(TiXmlNode *afterThis, const TiXmlNode &addThis) -{ - if (!afterThis || afterThis->parent != this) { - return nullptr; - } - - TiXmlNode *node = addThis.Clone(); - if (!node) { - return nullptr; - } - node->parent = this; - - node->prev = afterThis; - node->next = afterThis->next; - if (afterThis->next) { - afterThis->next->prev = node; - } - else { - assert(lastChild == afterThis); - lastChild = node; - } - afterThis->next = node; - return node; -} - -TiXmlNode *TiXmlNode::ReplaceChild(TiXmlNode *replaceThis, const TiXmlNode &withThis) -{ - if (replaceThis->parent != this) { - return nullptr; - } - - TiXmlNode *node = withThis.Clone(); - if (!node) { - return nullptr; - } - - node->next = replaceThis->next; - node->prev = replaceThis->prev; - - if (replaceThis->next) { - replaceThis->next->prev = node; - } - else { - lastChild = node; - } - - if (replaceThis->prev) { - replaceThis->prev->next = node; - } - else { - firstChild = node; - } - - delete replaceThis; - node->parent = this; - return node; -} - -bool TiXmlNode::RemoveChild(TiXmlNode *removeThis) -{ - if (removeThis->parent != this) { - assert(0); - return false; - } - - if (removeThis->next) { - removeThis->next->prev = removeThis->prev; - } - else { - lastChild = removeThis->prev; - } - - if (removeThis->prev) { - removeThis->prev->next = removeThis->next; - } - else { - firstChild = removeThis->next; - } - - delete removeThis; - return true; -} - -const TiXmlNode *TiXmlNode::FirstChild(const char *_value) const -{ - const TiXmlNode *node; - for (node = firstChild; node; node = node->next) { - if (strcmp(node->Value(), _value) == 0) { - return node; - } - } - return nullptr; -} - -TiXmlNode *TiXmlNode::FirstChild(const char *_value) -{ - TiXmlNode *node; - for (node = firstChild; node; node = node->next) { - if (strcmp(node->Value(), _value) == 0) { - return node; - } - } - return nullptr; -} - -const TiXmlNode *TiXmlNode::LastChild(const char *_value) const -{ - const TiXmlNode *node; - for (node = lastChild; node; node = node->prev) { - if (strcmp(node->Value(), _value) == 0) { - return node; - } - } - return nullptr; -} - -TiXmlNode *TiXmlNode::LastChild(const char *_value) -{ - TiXmlNode *node; - for (node = lastChild; node; node = node->prev) { - if (strcmp(node->Value(), _value) == 0) { - return node; - } - } - return nullptr; -} - -const TiXmlNode *TiXmlNode::IterateChildren(const TiXmlNode *previous) const -{ - if (!previous) { - return FirstChild(); - } - else { - assert(previous->parent == this); - return previous->NextSibling(); - } -} - -TiXmlNode *TiXmlNode::IterateChildren(TiXmlNode *previous) -{ - if (!previous) { - return FirstChild(); - } - else { - assert(previous->parent == this); - return previous->NextSibling(); - } -} - -const TiXmlNode *TiXmlNode::IterateChildren(const char *val, const TiXmlNode *previous) const -{ - if (!previous) { - return FirstChild(val); - } - else { - assert(previous->parent == this); - return previous->NextSibling(val); - } -} - -TiXmlNode *TiXmlNode::IterateChildren(const char *val, TiXmlNode *previous) -{ - if (!previous) { - return FirstChild(val); - } - else { - assert(previous->parent == this); - return previous->NextSibling(val); - } -} - -const TiXmlNode *TiXmlNode::NextSibling(const char *_value) const -{ - const TiXmlNode *node; - for (node = next; node; node = node->next) { - if (strcmp(node->Value(), _value) == 0) { - return node; - } - } - return nullptr; -} - -TiXmlNode *TiXmlNode::NextSibling(const char *_value) -{ - TiXmlNode *node; - for (node = next; node; node = node->next) { - if (strcmp(node->Value(), _value) == 0) { - return node; - } - } - return nullptr; -} - -const TiXmlNode *TiXmlNode::PreviousSibling(const char *_value) const -{ - const TiXmlNode *node; - for (node = prev; node; node = node->prev) { - if (strcmp(node->Value(), _value) == 0) { - return node; - } - } - return nullptr; -} - -TiXmlNode *TiXmlNode::PreviousSibling(const char *_value) -{ - TiXmlNode *node; - for (node = prev; node; node = node->prev) { - if (strcmp(node->Value(), _value) == 0) { - return node; - } - } - return nullptr; -} - -void TiXmlElement::RemoveAttribute(const char *name) -{ - TiXmlAttribute *node = attributeSet.Find(name); - if (node) { - attributeSet.Remove(node); - delete node; - } -} - -const TiXmlElement *TiXmlNode::FirstChildElement() const -{ - const TiXmlNode *node; - - for (node = FirstChild(); node; node = node->NextSibling()) { - if (node->ToElement()) { - return node->ToElement(); - } - } - return nullptr; -} - -TiXmlElement *TiXmlNode::FirstChildElement() -{ - TiXmlNode *node; - - for (node = FirstChild(); node; node = node->NextSibling()) { - if (node->ToElement()) { - return node->ToElement(); - } - } - return nullptr; -} - -const TiXmlElement *TiXmlNode::FirstChildElement(const char *_value) const -{ - const TiXmlNode *node; - - for (node = FirstChild(_value); node; node = node->NextSibling(_value)) { - if (node->ToElement()) { - return node->ToElement(); - } - } - return nullptr; -} - -TiXmlElement *TiXmlNode::FirstChildElement(const char *_value) -{ - TiXmlNode *node; - - for (node = FirstChild(_value); node; node = node->NextSibling(_value)) { - if (node->ToElement()) { - return node->ToElement(); - } - } - return nullptr; -} - -const TiXmlElement *TiXmlNode::NextSiblingElement() const -{ - const TiXmlNode *node; - - for (node = NextSibling(); node; node = node->NextSibling()) { - if (node->ToElement()) { - return node->ToElement(); - } - } - return nullptr; -} - -TiXmlElement *TiXmlNode::NextSiblingElement() -{ - TiXmlNode *node; - - for (node = NextSibling(); node; node = node->NextSibling()) { - if (node->ToElement()) { - return node->ToElement(); - } - } - return nullptr; -} - -const TiXmlElement *TiXmlNode::NextSiblingElement(const char *_value) const -{ - const TiXmlNode *node; - - for (node = NextSibling(_value); node; node = node->NextSibling(_value)) { - if (node->ToElement()) { - return node->ToElement(); - } - } - return nullptr; -} - -TiXmlElement *TiXmlNode::NextSiblingElement(const char *_value) -{ - TiXmlNode *node; - - for (node = NextSibling(_value); node; node = node->NextSibling(_value)) { - if (node->ToElement()) { - return node->ToElement(); - } - } - return nullptr; -} - -const TiXmlDocument *TiXmlNode::GetDocument() const -{ - const TiXmlNode *node; - - for (node = this; node; node = node->parent) { - if (node->ToDocument()) { - return node->ToDocument(); - } - } - return nullptr; -} - -TiXmlDocument *TiXmlNode::GetDocument() -{ - TiXmlNode *node; - - for (node = this; node; node = node->parent) { - if (node->ToDocument()) { - return node->ToDocument(); - } - } - return nullptr; -} - -TiXmlElement::TiXmlElement(const char *_value) : TiXmlNode(TiXmlNode::ELEMENT) -{ - firstChild = lastChild = nullptr; - value = _value; -} - -#ifdef TIXML_USE_STL -TiXmlElement::TiXmlElement(const std::string &_value) : TiXmlNode(TiXmlNode::ELEMENT) -{ - firstChild = lastChild = nullptr; - value = _value; -} -#endif - -TiXmlElement::TiXmlElement(const TiXmlElement ©) : TiXmlNode(TiXmlNode::ELEMENT) -{ - firstChild = lastChild = nullptr; - copy.CopyTo(this); -} - -void TiXmlElement::operator=(const TiXmlElement &base) -{ - ClearThis(); - base.CopyTo(this); -} - -TiXmlElement::~TiXmlElement() { ClearThis(); } - -void TiXmlElement::ClearThis() -{ - Clear(); - while (attributeSet.First()) { - TiXmlAttribute *node = attributeSet.First(); - attributeSet.Remove(node); - delete node; - } -} - -const char *TiXmlElement::Attribute(const char *name) const -{ - const TiXmlAttribute *node = attributeSet.Find(name); - - if (node) { - return node->Value(); - } - - return nullptr; -} - -const char *TiXmlElement::Attribute(const char *name, int *i) const -{ - const char *s = Attribute(name); - if (i) { - if (s) { - *i = atoi(s); - } - else { - *i = 0; - } - } - return s; -} - -const char *TiXmlElement::Attribute(const char *name, double *d) const -{ - const char *s = Attribute(name); - if (d) { - if (s) { - *d = atof(s); - } - else { - *d = 0; - } - } - return s; -} - -int TiXmlElement::QueryIntAttribute(const char *name, int *ival) const -{ - const TiXmlAttribute *node = attributeSet.Find(name); - if (!node) { - return TIXML_NO_ATTRIBUTE; - } - - return node->QueryIntValue(ival); -} - -int TiXmlElement::QueryDoubleAttribute(const char *name, double *dval) const -{ - const TiXmlAttribute *node = attributeSet.Find(name); - if (!node) { - return TIXML_NO_ATTRIBUTE; - } - - return node->QueryDoubleValue(dval); -} - -void TiXmlElement::SetAttribute(const char *name, int val) -{ - char buf[64]; -#if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF(buf, sizeof(buf), "%d", val); -#else - sprintf(buf, "%d", val); -#endif - SetAttribute(name, buf); -} - -void TiXmlElement::SetDoubleAttribute(const char *name, double val) -{ - char buf[256]; -#if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF(buf, sizeof(buf), "%f", val); -#else - sprintf(buf, "%f", val); -#endif - SetAttribute(name, buf); -} - -void TiXmlElement::SetAttribute(const char *name, const char *_value) -{ - TiXmlAttribute *node = attributeSet.Find(name); - if (node) { - node->SetValue(_value); - return; - } - - auto *attrib = new TiXmlAttribute(name, _value); - if (attrib) { - attributeSet.Add(attrib); - } - else { - TiXmlDocument *document = GetDocument(); - if (document) { - document->SetError(TIXML_ERROR_OUT_OF_MEMORY, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); - } - } -} - -void TiXmlElement::Print(FILE *cfile, int depth) const -{ - int i; - for (i = 0; i < depth; i++) { - fprintf(cfile, " "); - } - - fprintf(cfile, "<%s", value.c_str()); - - const TiXmlAttribute *attrib; - for (attrib = attributeSet.First(); attrib; attrib = attrib->Next()) { - fprintf(cfile, " "); - attrib->Print(cfile, depth); - } - - // There are 3 different formatting approaches: - // 1) An element without children is printed as a node - // 2) An element with only a text child is printed as text - // 3) An element with children is printed on multiple lines. - TiXmlNode *node; - if (!firstChild) { - fprintf(cfile, " />"); - } - else if (firstChild == lastChild && firstChild->ToText()) { - fprintf(cfile, ">"); - firstChild->Print(cfile, depth + 1); - fprintf(cfile, "", value.c_str()); - } - else { - fprintf(cfile, ">"); - - for (node = firstChild; node; node = node->NextSibling()) { - if (!node->ToText()) { - fprintf(cfile, "\n"); - } - node->Print(cfile, depth + 1); - } - fprintf(cfile, "\n"); - for (i = 0; i < depth; ++i) { - fprintf(cfile, " "); - } - fprintf(cfile, "", value.c_str()); - } -} - -void TiXmlElement::StreamOut(TIXML_OSTREAM *stream) const -{ - (*stream) << "<" << value; - - const TiXmlAttribute *attrib; - for (attrib = attributeSet.First(); attrib; attrib = attrib->Next()) { - (*stream) << " "; - attrib->StreamOut(stream); - } - - // If this node has children, give it a closing tag. Else - // make it an empty tag. - TiXmlNode *node; - if (firstChild) { - (*stream) << ">"; - - for (node = firstChild; node; node = node->NextSibling()) { - node->StreamOut(stream); - } - (*stream) << ""; - } - else { - (*stream) << " />"; - } -} - -void TiXmlElement::CopyTo(TiXmlElement *target) const -{ - // superclass: - TiXmlNode::CopyTo(target); - - // Element class: - // Clone the attributes, then clone the children. - const TiXmlAttribute *attribute = nullptr; - for (attribute = attributeSet.First(); attribute; attribute = attribute->Next()) { - target->SetAttribute(attribute->Name(), attribute->Value()); - } - - TiXmlNode *node = nullptr; - for (node = firstChild; node; node = node->NextSibling()) { - target->LinkEndChild(node->Clone()); - } -} - -TiXmlNode *TiXmlElement::Clone() const -{ - auto *clone = new TiXmlElement(Value()); - if (!clone) { - return nullptr; - } - - CopyTo(clone); - return clone; -} - -const char *TiXmlElement::GetText() const -{ - const TiXmlNode *child = this->FirstChild(); - if (child) { - const TiXmlText *childText = child->ToText(); - if (childText) { - return childText->Value(); - } - } - return nullptr; -} - -TiXmlDocument::TiXmlDocument() : TiXmlNode(TiXmlNode::DOCUMENT) { ClearError(); } - -TiXmlDocument::TiXmlDocument(const char *documentName) : TiXmlNode(TiXmlNode::DOCUMENT) -{ - value = documentName; - ClearError(); -} - -#ifdef TIXML_USE_STL -TiXmlDocument::TiXmlDocument(const std::string &documentName) : TiXmlNode(TiXmlNode::DOCUMENT) -{ - value = documentName; - ClearError(); -} -#endif - -TiXmlDocument::TiXmlDocument(const TiXmlDocument ©) : TiXmlNode(TiXmlNode::DOCUMENT) -{ - copy.CopyTo(this); -} - -void TiXmlDocument::operator=(const TiXmlDocument ©) -{ - Clear(); - copy.CopyTo(this); -} - -bool TiXmlDocument::LoadFile(TiXmlEncoding encoding) -{ - // See STL_STRING_BUG below. - const StringToBuffer buf(value); - - if (buf.buffer && LoadFile(buf.buffer, encoding)) { - return true; - } - - return false; -} - -bool TiXmlDocument::SaveFile() const -{ - // See STL_STRING_BUG below. - const StringToBuffer buf(value); - return (buf.buffer && SaveFile(buf.buffer)); -} - -bool TiXmlDocument::LoadFile(const char *filename, TiXmlEncoding encoding) -{ - // Delete the existing data: - Clear(); - location.Clear(); - - // There was a really terrifying little bug here. The code: - // value = filename - // in the STL case, cause the assignment method of the std::string to - // be called. What is strange, is that the std::string had the same - // address as it's c_str() method, and so bad things happen. Looks - // like a bug in the Microsoft STL implementation. - // See STL_STRING_BUG above. - // Fixed with the StringToBuffer class. - value = filename; - - // reading in binary mode so that tinyxml can normalize the EOL - FILE *file = fopen(value.c_str(), "rb"); - - if (file) { - // Get the file size, so we can pre-allocate the string. HUGE speed impact. - long length = 0; - fseek(file, 0, SEEK_END); - length = ftell(file); - fseek(file, 0, SEEK_SET); - - // Strange case, but good to handle up front. - if (length == 0) { - fclose(file); - return false; - } - - // If we have a file, assume it is all one big XML file, and read it in. - // The document parser may decide the document ends sooner than the entire file, however. - TIXML_STRING data; - data.reserve(length); - - // Subtle bug here. TinyXml did use fgets. But from the XML spec: - // 2.11 End-of-Line Handling - // - // - // ...the XML processor MUST behave as if it normalized all line breaks in external - // parsed entities (including the document entity) on input, before parsing, by translating - // both the two-character sequence #xD #xA and any #xD that is not followed by #xA to - // a single #xA character. - // - // - // It is not clear fgets does that, and certainly isn't clear it works cross platform. - // Generally, you expect fgets to translate from the convention of the OS to the c/unix - // convention, and not work generally. - - /* - while( fgets( buf, sizeof(buf), file ) ) - { - data += buf; - } - */ - - char *buf = new char[length + 1]; - buf[0] = 0; - - if (fread(buf, length, 1, file) != 1) { - // if ( fread( buf, 1, length, file ) != (size_t)length ) { - SetError(TIXML_ERROR_OPENING_FILE, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); - fclose(file); - return false; - } - fclose(file); - - const char *lastPos = buf; - const char *p = buf; - - buf[length] = 0; - while (*p) { - assert(p < (buf + length)); - if (*p == 0xa) { - // Newline character. No special rules for this. Append all the characters - // since the last string, and include the newline. - data.append(lastPos, p - lastPos + 1); // append, include the newline - ++p; // move past the newline - lastPos = p; // and point to the new buffer (may be 0) - assert(p <= (buf + length)); - } - else if (*p == 0xd) { - // Carriage return. Append what we have so far, then - // handle moving forward in the buffer. - if ((p - lastPos) > 0) { - data.append(lastPos, p - lastPos); // do not add the CR - } - data += (char)0xa; // a proper newline - - if (*(p + 1) == 0xa) { - // Carriage return - new line sequence - p += 2; - lastPos = p; - assert(p <= (buf + length)); - } - else { - // it was followed by something else...that is presumably characters again. - ++p; - lastPos = p; - assert(p <= (buf + length)); - } - } - else { - ++p; - } - } - // Handle any left over characters. - if (p - lastPos) { - data.append(lastPos, p - lastPos); - } - delete[] buf; - buf = nullptr; - - Parse(data.c_str(), nullptr, encoding); - - if (Error()) { - return false; - } - else { - return true; - } - } - SetError(TIXML_ERROR_OPENING_FILE, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); - return false; -} - -bool TiXmlDocument::SaveFile(const char *filename) const -{ - // The old c stuff lives on... - FILE *fp = fopen(filename, "w"); - if (fp) { - if (useMicrosoftBOM) { - const unsigned char TIXML_UTF_LEAD_0 = 0xefU; - const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; - const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; - - fputc(TIXML_UTF_LEAD_0, fp); - fputc(TIXML_UTF_LEAD_1, fp); - fputc(TIXML_UTF_LEAD_2, fp); - } - Print(fp, 0); - fclose(fp); - return true; - } - return false; -} - -void TiXmlDocument::CopyTo(TiXmlDocument *target) const -{ - TiXmlNode::CopyTo(target); - - target->error = error; - target->errorDesc = errorDesc.c_str(); - - TiXmlNode *node = nullptr; - for (node = firstChild; node; node = node->NextSibling()) { - target->LinkEndChild(node->Clone()); - } -} - -TiXmlNode *TiXmlDocument::Clone() const -{ - auto *clone = new TiXmlDocument(); - if (!clone) { - return nullptr; - } - - CopyTo(clone); - return clone; -} - -void TiXmlDocument::Print(FILE *cfile, int depth) const -{ - const TiXmlNode *node; - for (node = FirstChild(); node; node = node->NextSibling()) { - node->Print(cfile, depth); - fprintf(cfile, "\n"); - } -} - -void TiXmlDocument::StreamOut(TIXML_OSTREAM *out) const -{ - const TiXmlNode *node; - for (node = FirstChild(); node; node = node->NextSibling()) { - node->StreamOut(out); - - // Special rule for streams: stop after the root element. - // The stream in code will only read one element, so don't - // write more than one. - if (node->ToElement()) { - break; - } - } -} - -const TiXmlAttribute *TiXmlAttribute::Next() const -{ - // We are using knowledge of the sentinel. The sentinel - // have a value or name. - if (next->value.empty() && next->name.empty()) { - return nullptr; - } - return next; -} - -TiXmlAttribute *TiXmlAttribute::Next() -{ - // We are using knowledge of the sentinel. The sentinel - // have a value or name. - if (next->value.empty() && next->name.empty()) { - return nullptr; - } - return next; -} - -const TiXmlAttribute *TiXmlAttribute::Previous() const -{ - // We are using knowledge of the sentinel. The sentinel - // have a value or name. - if (prev->value.empty() && prev->name.empty()) { - return nullptr; - } - return prev; -} - -TiXmlAttribute *TiXmlAttribute::Previous() -{ - // We are using knowledge of the sentinel. The sentinel - // have a value or name. - if (prev->value.empty() && prev->name.empty()) { - return nullptr; - } - return prev; -} - -void TiXmlAttribute::Print(FILE *cfile, int /*depth*/) const -{ - TIXML_STRING n, v; - - PutString(name, &n); - PutString(value, &v); - - if (value.find('\"') == TIXML_STRING::npos) { - fprintf(cfile, "%s=\"%s\"", n.c_str(), v.c_str()); - } - else { - fprintf(cfile, "%s='%s'", n.c_str(), v.c_str()); - } -} - -void TiXmlAttribute::StreamOut(TIXML_OSTREAM *stream) const -{ - if (value.find('\"') != TIXML_STRING::npos) { - PutString(name, stream); - (*stream) << "=" - << "'"; - PutString(value, stream); - (*stream) << "'"; - } - else { - PutString(name, stream); - (*stream) << "=" - << "\""; - PutString(value, stream); - (*stream) << "\""; - } -} - -int TiXmlAttribute::QueryIntValue(int *ival) const -{ - if (sscanf(value.c_str(), "%d", ival) == 1) { - return TIXML_SUCCESS; - } - return TIXML_WRONG_TYPE; -} - -int TiXmlAttribute::QueryDoubleValue(double *dval) const -{ - if (sscanf(value.c_str(), "%lf", dval) == 1) { - return TIXML_SUCCESS; - } - return TIXML_WRONG_TYPE; -} - -void TiXmlAttribute::SetIntValue(int _value) -{ - char buf[64]; -#if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF(buf, sizeof(buf), "%d", _value); -#else - sprintf(buf, "%d", _value); -#endif - SetValue(buf); -} - -void TiXmlAttribute::SetDoubleValue(double _value) -{ - char buf[256]; -#if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF(buf, sizeof(buf), "%lf", _value); -#else - sprintf(buf, "%lf", _value); -#endif - SetValue(buf); -} - -int TiXmlAttribute::IntValue() const { return atoi(value.c_str()); } - -double TiXmlAttribute::DoubleValue() const { return atof(value.c_str()); } - -TiXmlComment::TiXmlComment(const TiXmlComment ©) : TiXmlNode(TiXmlNode::COMMENT) -{ - copy.CopyTo(this); -} - -void TiXmlComment::operator=(const TiXmlComment &base) -{ - Clear(); - base.CopyTo(this); -} - -void TiXmlComment::Print(FILE *cfile, int depth) const -{ - for (int i = 0; i < depth; i++) { - fputs(" ", cfile); - } - fprintf(cfile, "", value.c_str()); -} - -void TiXmlComment::StreamOut(TIXML_OSTREAM *stream) const -{ - (*stream) << ""; -} - -void TiXmlComment::CopyTo(TiXmlComment *target) const { TiXmlNode::CopyTo(target); } - -TiXmlNode *TiXmlComment::Clone() const -{ - auto *clone = new TiXmlComment(); - - if (!clone) { - return nullptr; - } - - CopyTo(clone); - return clone; -} - -void TiXmlText::Print(FILE *cfile, int depth) const -{ - if (cdata) { - int i; - fprintf(cfile, "\n"); - for (i = 0; i < depth; i++) { - fprintf(cfile, " "); - } - fprintf(cfile, "\n"); - } - else { - TIXML_STRING buffer; - PutString(value, &buffer); - fprintf(cfile, "%s", buffer.c_str()); - } -} - -void TiXmlText::StreamOut(TIXML_OSTREAM *stream) const -{ - if (cdata) { - (*stream) << ""; - } - else { - PutString(value, stream); - } -} - -void TiXmlText::CopyTo(TiXmlText *target) const -{ - TiXmlNode::CopyTo(target); - target->cdata = cdata; -} - -TiXmlNode *TiXmlText::Clone() const -{ - TiXmlText *clone = nullptr; - clone = new TiXmlText(""); - - if (!clone) { - return nullptr; - } - - CopyTo(clone); - return clone; -} - -TiXmlDeclaration::TiXmlDeclaration(const char *_version, const char *_encoding, - const char *_standalone) - : TiXmlNode(TiXmlNode::DECLARATION), version(_version), encoding(_encoding), - standalone(_standalone) -{ -} - -#ifdef TIXML_USE_STL -TiXmlDeclaration::TiXmlDeclaration(const std::string &_version, const std::string &_encoding, - const std::string &_standalone) - : TiXmlNode(TiXmlNode::DECLARATION), version(_version), encoding(_encoding), - standalone(_standalone) -{ -} -#endif - -TiXmlDeclaration::TiXmlDeclaration(const TiXmlDeclaration ©) - : TiXmlNode(TiXmlNode::DECLARATION) -{ - copy.CopyTo(this); -} - -void TiXmlDeclaration::operator=(const TiXmlDeclaration ©) -{ - Clear(); - copy.CopyTo(this); -} - -void TiXmlDeclaration::Print(FILE *cfile, int /*depth*/) const -{ - fprintf(cfile, ""); -} - -void TiXmlDeclaration::StreamOut(TIXML_OSTREAM *stream) const -{ - (*stream) << ""; -} - -void TiXmlDeclaration::CopyTo(TiXmlDeclaration *target) const -{ - TiXmlNode::CopyTo(target); - - target->version = version; - target->encoding = encoding; - target->standalone = standalone; -} - -TiXmlNode *TiXmlDeclaration::Clone() const -{ - auto *clone = new TiXmlDeclaration(); - - if (!clone) { - return nullptr; - } - - CopyTo(clone); - return clone; -} - -void TiXmlUnknown::Print(FILE *cfile, int depth) const -{ - for (int i = 0; i < depth; i++) { - fprintf(cfile, " "); - } - fprintf(cfile, "<%s>", value.c_str()); -} - -void TiXmlUnknown::StreamOut(TIXML_OSTREAM *stream) const -{ - (*stream) << "<" << value << ">"; // Don't use entities here! It is unknown. -} - -void TiXmlUnknown::CopyTo(TiXmlUnknown *target) const { TiXmlNode::CopyTo(target); } - -TiXmlNode *TiXmlUnknown::Clone() const -{ - auto *clone = new TiXmlUnknown(); - - if (!clone) { - return nullptr; - } - - CopyTo(clone); - return clone; -} - -TiXmlAttributeSet::TiXmlAttributeSet() -{ - sentinel.next = &sentinel; - sentinel.prev = &sentinel; -} - -TiXmlAttributeSet::~TiXmlAttributeSet() -{ - assert(sentinel.next == &sentinel); - assert(sentinel.prev == &sentinel); -} - -void TiXmlAttributeSet::Add(TiXmlAttribute *addMe) -{ - assert(!Find(addMe->Name())); // Shouldn't be multiply adding to the set. - - addMe->next = &sentinel; - addMe->prev = sentinel.prev; - - sentinel.prev->next = addMe; - sentinel.prev = addMe; -} - -void TiXmlAttributeSet::Remove(TiXmlAttribute *removeMe) -{ - TiXmlAttribute *node; - - for (node = sentinel.next; node != &sentinel; node = node->next) { - if (node == removeMe) { - node->prev->next = node->next; - node->next->prev = node->prev; - node->next = nullptr; - node->prev = nullptr; - return; - } - } - assert(0); // we tried to remove a non-linked attribute. -} - -const TiXmlAttribute *TiXmlAttributeSet::Find(const char *name) const -{ - const TiXmlAttribute *node; - - for (node = sentinel.next; node != &sentinel; node = node->next) { - if (node->name == name) { - return node; - } - } - return nullptr; -} - -TiXmlAttribute *TiXmlAttributeSet::Find(const char *name) -{ - TiXmlAttribute *node; - - for (node = sentinel.next; node != &sentinel; node = node->next) { - if (node->name == name) { - return node; - } - } - return nullptr; -} - -#ifdef TIXML_USE_STL -TIXML_ISTREAM &operator>>(TIXML_ISTREAM &in, TiXmlNode &base) -{ - TIXML_STRING tag; - tag.reserve(8 * 1000); - base.StreamIn(&in, &tag); - - base.Parse(tag.c_str(), nullptr, TIXML_DEFAULT_ENCODING); - return in; -} -#endif - -TIXML_OSTREAM &operator<<(TIXML_OSTREAM &out, const TiXmlNode &base) -{ - base.StreamOut(&out); - return out; -} - -#ifdef TIXML_USE_STL -std::string &operator<<(std::string &out, const TiXmlNode &base) -{ - std::ostringstream os_stream(std::ostringstream::out); - base.StreamOut(&os_stream); - - out.append(os_stream.str()); - return out; -} -#endif - -TiXmlHandle TiXmlHandle::FirstChild() const -{ - if (node) { - TiXmlNode *child = node->FirstChild(); - if (child) { - return {child}; - } - } - return {nullptr}; -} - -TiXmlHandle TiXmlHandle::FirstChild(const char *value) const -{ - if (node) { - TiXmlNode *child = node->FirstChild(value); - if (child) { - return {child}; - } - } - return {nullptr}; -} - -TiXmlHandle TiXmlHandle::FirstChildElement() const -{ - if (node) { - TiXmlElement *child = node->FirstChildElement(); - if (child) { - return {child}; - } - } - return {nullptr}; -} - -TiXmlHandle TiXmlHandle::FirstChildElement(const char *value) const -{ - if (node) { - TiXmlElement *child = node->FirstChildElement(value); - if (child) { - return {child}; - } - } - return {nullptr}; -} - -TiXmlHandle TiXmlHandle::Child(int count) const -{ - if (node) { - int i; - TiXmlNode *child = node->FirstChild(); - for (i = 0; child && i < count; child = child->NextSibling(), ++i) { - // nothing - } - if (child) { - return {child}; - } - } - return {nullptr}; -} - -TiXmlHandle TiXmlHandle::Child(const char *value, int count) const -{ - if (node) { - int i; - TiXmlNode *child = node->FirstChild(value); - for (i = 0; child && i < count; child = child->NextSibling(value), ++i) { - // nothing - } - if (child) { - return {child}; - } - } - return {nullptr}; -} - -TiXmlHandle TiXmlHandle::ChildElement(int count) const -{ - if (node) { - int i; - TiXmlElement *child = node->FirstChildElement(); - for (i = 0; child && i < count; child = child->NextSiblingElement(), ++i) { - // nothing - } - if (child) { - return {child}; - } - } - return {nullptr}; -} - -TiXmlHandle TiXmlHandle::ChildElement(const char *value, int count) const -{ - if (node) { - int i; - TiXmlElement *child = node->FirstChildElement(value); - for (i = 0; child && i < count; child = child->NextSiblingElement(value), ++i) { - // nothing - } - if (child) { - return {child}; - } - } - return {nullptr}; -} diff --git a/src/core/tinyxml.h b/src/core/tinyxml.h deleted file mode 100644 index 209cf7c63b..0000000000 --- a/src/core/tinyxml.h +++ /dev/null @@ -1,1602 +0,0 @@ -/* -www.sourceforge.net/projects/tinyxml -Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#ifndef TINYXML_INCLUDED -#define TINYXML_INCLUDED - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4530) -#pragma warning(disable : 4786) -#endif - -#include -#include -#include -#include -#include - -// Help out windows: -#if defined(_DEBUG) && !defined(DEBUG) -#define DEBUG -#endif - -#if defined(DEBUG) && defined(_MSC_VER) -#include -#define TIXML_LOG OutputDebugString -#else -#define TIXML_LOG printf -#endif - -// Gambit change: we use STL, so we define this here. -#define TIXML_USE_STL 1 - -#ifdef TIXML_USE_STL -#include -#include -#define TIXML_STRING std::string -#define TIXML_ISTREAM std::istream -#define TIXML_OSTREAM std::ostream -#else -#include "tinystr.h" -#define TIXML_STRING TiXmlString -#define TIXML_OSTREAM TiXmlOutStream -#endif - -// Deprecated library function hell. Compilers want to use the -// new safe versions. This probably doesn't fully address the problem, -// but it gets closer. There are too many compilers for me to fully -// test. If you get compilation troubles, undefine TIXML_SAFE - -#define TIXML_SAFE // TinyXml isn't fully buffer overrun protected, safe code. This is work in - // progress. -#ifdef TIXML_SAFE -#if defined(_MSC_VER) && (_MSC_VER >= 1200) -// Microsoft visual studio, version 6 and higher. -// #pragma message( "Using _sn* functions." ) -#define TIXML_SNPRINTF _snprintf -#define TIXML_SNSCANF _snscanf -#elif defined(__GNUC__) && (__GNUC__ >= 3) -// GCC version 3 and higher.s -// #warning( "Using sn* functions." ) -#define TIXML_SNPRINTF snprintf -#define TIXML_SNSCANF snscanf -#endif -#endif - -class TiXmlDocument; -class TiXmlElement; -class TiXmlComment; -class TiXmlUnknown; -class TiXmlAttribute; -class TiXmlText; -class TiXmlDeclaration; -class TiXmlParsingData; - -const int TIXML_MAJOR_VERSION = 2; -const int TIXML_MINOR_VERSION = 4; -const int TIXML_PATCH_VERSION = 2; - -/* Internal structure for tracking location of items - in the XML file. -*/ -struct TiXmlCursor { - TiXmlCursor() = default; - void Clear() { row = col = -1; } - - int row{-1}; // 0 based. - int col{-1}; // 0 based. -}; - -// Only used by Attribute::Query functions -enum { TIXML_SUCCESS, TIXML_NO_ATTRIBUTE, TIXML_WRONG_TYPE }; - -// Used by the parsing routines. -enum TiXmlEncoding { TIXML_ENCODING_UNKNOWN, TIXML_ENCODING_UTF8, TIXML_ENCODING_LEGACY }; - -const TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN; - -/** TiXmlBase is a base class for every class in TinyXml. - It does little except to establish that TinyXml classes - can be printed and provide some utility functions. - - In XML, the document and elements can contain - other elements and other types of nodes. - - @verbatim - A Document can contain: Element (container or leaf) - Comment (leaf) - Unknown (leaf) - Declaration( leaf ) - - An Element can contain: Element (container or leaf) - Text (leaf) - Attributes (not on tree) - Comment (leaf) - Unknown (leaf) - - A Decleration contains: Attributes (not on tree) - @endverbatim -*/ -class TiXmlBase { - friend class TiXmlNode; - friend class TiXmlElement; - friend class TiXmlDocument; - -public: - TiXmlBase() = default; - virtual ~TiXmlBase() = default; - - /** All TinyXml classes can print themselves to a filestream. - This is a formatted print, and will insert tabs and newlines. - - (For an unformatted stream, use the << operator.) - */ - virtual void Print(FILE *cfile, int depth) const = 0; - - /** The world does not agree on whether white space should be kept or - not. In order to make everyone happy, these global, static functions - are provided to set whether or not TinyXml will condense all white space - into a single space or not. The default is to condense. Note changing this - values is not thread safe. - */ - static void SetCondenseWhiteSpace(bool condense) { condenseWhiteSpace = condense; } - - /// Return the current white space setting. - static bool IsWhiteSpaceCondensed() { return condenseWhiteSpace; } - - /** Return the position, in the original source file, of this node or attribute. - The row and column are 1-based. (That is the first row and first column is - 1,1). If the returns values are 0 or less, then the parser does not have - a row and column value. - - Generally, the row and column value will be set when the TiXmlDocument::Load(), - TiXmlDocument::LoadFile(), or any TiXmlNode::Parse() is called. It will NOT be set - when the DOM was created from operator>>. - - The values reflect the initial load. Once the DOM is modified programmatically - (by adding or changing nodes and attributes) the new values will NOT update to - reflect changes in the document. - - There is a minor performance cost to computing the row and column. Computation - can be disabled if TiXmlDocument::SetTabSize() is called with 0 as the value. - - @sa TiXmlDocument::SetTabSize() - */ - int Row() const { return location.row + 1; } - int Column() const { return location.col + 1; } ///< See Row() - - void SetUserData(void *user) { userData = user; } - void *GetUserData() { return userData; } - - // Table that returs, for a given lead byte, the total number of bytes - // in the UTF-8 sequence. - static const int utf8ByteTable[256]; - - virtual const char *Parse(const char *p, TiXmlParsingData *data, - TiXmlEncoding encoding /*= TIXML_ENCODING_UNKNOWN */) = 0; - - enum { - TIXML_NO_ERROR = 0, - TIXML_ERROR, - TIXML_ERROR_OPENING_FILE, - TIXML_ERROR_OUT_OF_MEMORY, - TIXML_ERROR_PARSING_ELEMENT, - TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, - TIXML_ERROR_READING_ELEMENT_VALUE, - TIXML_ERROR_READING_ATTRIBUTES, - TIXML_ERROR_PARSING_EMPTY, - TIXML_ERROR_READING_END_TAG, - TIXML_ERROR_PARSING_UNKNOWN, - TIXML_ERROR_PARSING_COMMENT, - TIXML_ERROR_PARSING_DECLARATION, - TIXML_ERROR_DOCUMENT_EMPTY, - TIXML_ERROR_EMBEDDED_NULL, - TIXML_ERROR_PARSING_CDATA, - - TIXML_ERROR_STRING_COUNT - }; - -protected: - // See STL_STRING_BUG - // Utility class to overcome a bug. - class StringToBuffer { - public: - StringToBuffer(const TIXML_STRING &str); - ~StringToBuffer(); - char *buffer; - }; - - static const char *SkipWhiteSpace(const char *, TiXmlEncoding encoding); - inline static bool IsWhiteSpace(char c) - { - return (isspace((unsigned char)c) || c == '\n' || c == '\r'); - } - - virtual void StreamOut(TIXML_OSTREAM *) const = 0; - -#ifdef TIXML_USE_STL - static bool StreamWhiteSpace(TIXML_ISTREAM *in, TIXML_STRING *tag); - static bool StreamTo(TIXML_ISTREAM *in, int character, TIXML_STRING *tag); -#endif - - /* Reads an XML name into the string provided. Returns - a pointer just past the last character of the name, - or 0 if the function has an error. - */ - static const char *ReadName(const char *p, TIXML_STRING *name, TiXmlEncoding encoding); - - /* Reads text. Returns a pointer past the given end tag. - Wickedly complex options, but it keeps the (sensitive) code in one place. - */ - static const char *ReadText(const char *in, // where to start - TIXML_STRING *text, // the string read - bool ignoreWhiteSpace, // whether to keep the white space - const char *endTag, // what ends this text - bool ignoreCase, // whether to ignore case in the end tag - TiXmlEncoding encoding); // the current encoding - - // If an entity has been found, transform it into a character. - static const char *GetEntity(const char *in, char *value, int *length, TiXmlEncoding encoding); - - // Get a character, while interpreting entities. - // The length can be from 0 to 4 bytes. - inline static const char *GetChar(const char *p, char *_value, int *length, - TiXmlEncoding encoding) - { - assert(p); - if (encoding == TIXML_ENCODING_UTF8) { - *length = utf8ByteTable[*(reinterpret_cast(p))]; - assert(*length >= 0 && *length < 5); - } - else { - *length = 1; - } - - if (*length == 1) { - if (*p == '&') { - return GetEntity(p, _value, length, encoding); - } - *_value = *p; - return p + 1; - } - else if (*length) { - // strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe), - // and the null terminator isn't needed - for (int i = 0; p[i] && i < *length; ++i) { - _value[i] = p[i]; - } - return p + (*length); - } - else { - // Not valid text. - return nullptr; - } - } - - // Puts a string to a stream, expanding entities as it goes. - // Note this should not contian the '<', '>', etc, or they will be transformed into entities! - static void PutString(const TIXML_STRING &str, TIXML_OSTREAM *out); - - static void PutString(const TIXML_STRING &str, TIXML_STRING *out); - - // Return true if the next characters in the stream are any of the endTag sequences. - // Ignore case only works for english, and should only be relied on when comparing - // to English words: StringEqual( p, "version", true ) is fine. - static bool StringEqual(const char *p, const char *endTag, bool ignoreCase, - TiXmlEncoding encoding); - - static const char *errorString[TIXML_ERROR_STRING_COUNT]; - - TiXmlCursor location; - - /// Field containing a generic user pointer - void *userData{nullptr}; - - // None of these methods are reliable for any language except English. - // Good for approximation, not great for accuracy. - static int IsAlpha(unsigned char anyByte, TiXmlEncoding encoding); - static int IsAlphaNum(unsigned char anyByte, TiXmlEncoding encoding); - inline static int ToLower(int v, TiXmlEncoding encoding) - { - if (encoding == TIXML_ENCODING_UTF8) { - if (v < 128) { - return tolower(v); - } - return v; - } - else { - return tolower(v); - } - } - static void ConvertUTF32ToUTF8(unsigned long input, char *output, int *length); - -private: - TiXmlBase(const TiXmlBase &); // not implemented. - void operator=(const TiXmlBase &base); // not allowed. - - struct Entity { - const char *str; - unsigned int strLength; - char chr; - }; - enum { - NUM_ENTITY = 5, - MAX_ENTITY_LENGTH = 6 - - }; - static Entity entity[NUM_ENTITY]; - static bool condenseWhiteSpace; -}; - -/** The parent class for everything in the Document Object Model. - (Except for attributes). - Nodes have siblings, a parent, and children. A node can be - in a document, or stand on its own. The type of a TiXmlNode - can be queried, and it can be cast to its more defined type. -*/ -class TiXmlNode : public TiXmlBase { - friend class TiXmlDocument; - friend class TiXmlElement; - -public: -#ifdef TIXML_USE_STL - - /** An input stream operator, for every class. Tolerant of newlines and - formatting, but doesn't expect them. - */ - friend std::istream &operator>>(std::istream &in, TiXmlNode &base); - - /** An output stream operator, for every class. Note that this outputs - without any newlines or formatting, as opposed to Print(), which - includes tabs and new lines. - - The operator<< and operator>> are not completely symmetric. Writing - a node to a stream is very well defined. You'll get a nice stream - of output, without any extra whitespace or newlines. - - But reading is not as well defined. (As it always is.) If you create - a TiXmlElement (for example) and read that from an input stream, - the text needs to define an element or junk will result. This is - true of all input streams, but it's worth keeping in mind. - - A TiXmlDocument will read nodes until it reads a root element, and - all the children of that root element. - */ - friend std::ostream &operator<<(std::ostream &out, const TiXmlNode &base); - - /// Appends the XML node or attribute to a std::string. - friend std::string &operator<<(std::string &out, const TiXmlNode &base); - -#else - // Used internally, not part of the public API. - friend TIXML_OSTREAM &operator<<(TIXML_OSTREAM &out, const TiXmlNode &base); -#endif - - /** The types of XML nodes supported by TinyXml. (All the - unsupported types are picked up by UNKNOWN.) - */ - enum NodeType { DOCUMENT, ELEMENT, COMMENT, UNKNOWN, TEXT, DECLARATION, TYPECOUNT }; - - ~TiXmlNode() override; - - /** The meaning of 'value' changes for the specific type of - TiXmlNode. - @verbatim - Document: filename of the xml file - Element: name of the element - Comment: the comment text - Unknown: the tag contents - Text: the text string - @endverbatim - - The subclasses will wrap this function. - */ - const char *Value() const { return value.c_str(); } - -#ifdef TIXML_USE_STL - /** Return Value() as a std::string. If you only use STL, - this is more efficient than calling Value(). - Only available in STL mode. - */ - const std::string &ValueStr() const { return value; } -#endif - - /** Changes the value of the node. Defined as: - @verbatim - Document: filename of the xml file - Element: name of the element - Comment: the comment text - Unknown: the tag contents - Text: the text string - @endverbatim - */ - void SetValue(const char *_value) { value = _value; } - -#ifdef TIXML_USE_STL - /// STL std::string form. - void SetValue(const std::string &_value) - { - StringToBuffer const buf(_value); - SetValue(buf.buffer ? buf.buffer : ""); - } -#endif - - /// Delete all the children of this node. Does not affect 'this'. - void Clear(); - - /// One step up the DOM. - TiXmlNode *Parent() { return parent; } - const TiXmlNode *Parent() const { return parent; } - - const TiXmlNode *FirstChild() const - { - return firstChild; - } ///< The first child of this node. Will be null if there are no children. - TiXmlNode *FirstChild() { return firstChild; } - const TiXmlNode * - FirstChild(const char *value) const; ///< The first child of this node with the matching 'value'. - ///< Will be null if none found. - TiXmlNode *FirstChild(const char *value); ///< The first child of this node with the matching - ///< 'value'. Will be null if none found. - - const TiXmlNode *LastChild() const - { - return lastChild; - } /// The last child of this node. Will be null if there are no children. - TiXmlNode *LastChild() { return lastChild; } - const TiXmlNode * - LastChild(const char *value) const; /// The last child of this node matching 'value'. Will be - /// null if there are no children. - TiXmlNode *LastChild(const char *value); - -#ifdef TIXML_USE_STL - const TiXmlNode *FirstChild(const std::string &_value) const - { - return FirstChild(_value.c_str()); - } ///< STL std::string form. - TiXmlNode *FirstChild(const std::string &_value) - { - return FirstChild(_value.c_str()); - } ///< STL std::string form. - const TiXmlNode *LastChild(const std::string &_value) const - { - return LastChild(_value.c_str()); - } ///< STL std::string form. - TiXmlNode *LastChild(const std::string &_value) - { - return LastChild(_value.c_str()); - } ///< STL std::string form. -#endif - - /** An alternate way to walk the children of a node. - One way to iterate over nodes is: - @verbatim - for( child = parent->FirstChild(); child; child = child->NextSibling() ) - @endverbatim - - IterateChildren does the same thing with the syntax: - @verbatim - child = 0; - while( child = parent->IterateChildren( child ) ) - @endverbatim - - IterateChildren takes the previous child as input and finds - the next one. If the previous child is null, it returns the - first. IterateChildren will return null when done. - */ - const TiXmlNode *IterateChildren(const TiXmlNode *previous) const; - TiXmlNode *IterateChildren(TiXmlNode *previous); - - /// This flavor of IterateChildren searches for children with a particular 'value' - const TiXmlNode *IterateChildren(const char *value, const TiXmlNode *previous) const; - TiXmlNode *IterateChildren(const char *value, TiXmlNode *previous); - -#ifdef TIXML_USE_STL - const TiXmlNode *IterateChildren(const std::string &_value, const TiXmlNode *previous) const - { - return IterateChildren(_value.c_str(), previous); - } ///< STL std::string form. - TiXmlNode *IterateChildren(const std::string &_value, TiXmlNode *previous) - { - return IterateChildren(_value.c_str(), previous); - } ///< STL std::string form. -#endif - - /** Add a new node related to this. Adds a child past the LastChild. - Returns a pointer to the new object or NULL if an error occured. - */ - TiXmlNode *InsertEndChild(const TiXmlNode &addThis); - - /** Add a new node related to this. Adds a child past the LastChild. - - NOTE: the node to be added is passed by pointer, and will be - henceforth owned (and deleted) by tinyXml. This method is efficient - and avoids an extra copy, but should be used with care as it - uses a different memory model than the other insert functions. - - @sa InsertEndChild - */ - TiXmlNode *LinkEndChild(TiXmlNode *addThis); - - /** Add a new node related to this. Adds a child before the specified child. - Returns a pointer to the new object or NULL if an error occured. - */ - TiXmlNode *InsertBeforeChild(TiXmlNode *beforeThis, const TiXmlNode &addThis); - - /** Add a new node related to this. Adds a child after the specified child. - Returns a pointer to the new object or NULL if an error occured. - */ - TiXmlNode *InsertAfterChild(TiXmlNode *afterThis, const TiXmlNode &addThis); - - /** Replace a child of this node. - Returns a pointer to the new object or NULL if an error occured. - */ - TiXmlNode *ReplaceChild(TiXmlNode *replaceThis, const TiXmlNode &withThis); - - /// Delete a child of this node. - bool RemoveChild(TiXmlNode *removeThis); - - /// Navigate to a sibling node. - const TiXmlNode *PreviousSibling() const { return prev; } - TiXmlNode *PreviousSibling() { return prev; } - - /// Navigate to a sibling node. - const TiXmlNode *PreviousSibling(const char *) const; - TiXmlNode *PreviousSibling(const char *); - -#ifdef TIXML_USE_STL - const TiXmlNode *PreviousSibling(const std::string &_value) const - { - return PreviousSibling(_value.c_str()); - } ///< STL std::string form. - TiXmlNode *PreviousSibling(const std::string &_value) - { - return PreviousSibling(_value.c_str()); - } ///< STL std::string form. - const TiXmlNode *NextSibling(const std::string &_value) const - { - return NextSibling(_value.c_str()); - } ///< STL std::string form. - TiXmlNode *NextSibling(const std::string &_value) - { - return NextSibling(_value.c_str()); - } ///< STL std::string form. -#endif - - /// Navigate to a sibling node. - const TiXmlNode *NextSibling() const { return next; } - TiXmlNode *NextSibling() { return next; } - - /// Navigate to a sibling node with the given 'value'. - const TiXmlNode *NextSibling(const char *) const; - TiXmlNode *NextSibling(const char *); - - /** Convenience function to get through elements. - Calls NextSibling and ToElement. Will skip all non-Element - nodes. Returns 0 if there is not another element. - */ - const TiXmlElement *NextSiblingElement() const; - TiXmlElement *NextSiblingElement(); - - /** Convenience function to get through elements. - Calls NextSibling and ToElement. Will skip all non-Element - nodes. Returns 0 if there is not another element. - */ - const TiXmlElement *NextSiblingElement(const char *) const; - TiXmlElement *NextSiblingElement(const char *); - -#ifdef TIXML_USE_STL - const TiXmlElement *NextSiblingElement(const std::string &_value) const - { - return NextSiblingElement(_value.c_str()); - } ///< STL std::string form. - TiXmlElement *NextSiblingElement(const std::string &_value) - { - return NextSiblingElement(_value.c_str()); - } ///< STL std::string form. -#endif - - /// Convenience function to get through elements. - const TiXmlElement *FirstChildElement() const; - TiXmlElement *FirstChildElement(); - - /// Convenience function to get through elements. - const TiXmlElement *FirstChildElement(const char *value) const; - TiXmlElement *FirstChildElement(const char *value); - -#ifdef TIXML_USE_STL - const TiXmlElement *FirstChildElement(const std::string &_value) const - { - return FirstChildElement(_value.c_str()); - } ///< STL std::string form. - TiXmlElement *FirstChildElement(const std::string &_value) - { - return FirstChildElement(_value.c_str()); - } ///< STL std::string form. -#endif - - /** Query the type (as an enumerated value, above) of this node. - The possible types are: DOCUMENT, ELEMENT, COMMENT, - UNKNOWN, TEXT, and DECLARATION. - */ - int Type() const { return type; } - - /** Return a pointer to the Document this node lives in. - Returns null if not in a document. - */ - const TiXmlDocument *GetDocument() const; - TiXmlDocument *GetDocument(); - - /// Returns true if this node has no children. - bool NoChildren() const { return !firstChild; } - - const TiXmlDocument *ToDocument() const; - const TiXmlElement *ToElement() const; - const TiXmlComment *ToComment() const; - const TiXmlUnknown *ToUnknown() const; - const TiXmlText *ToText() const; - const TiXmlDeclaration *ToDeclaration() const; - - TiXmlDocument *ToDocument(); - TiXmlElement *ToElement(); - TiXmlComment *ToComment(); - TiXmlUnknown *ToUnknown(); - TiXmlText *ToText(); - TiXmlDeclaration *ToDeclaration(); - - /** Create an exact duplicate of this node and return it. The memory must be deleted - by the caller. - */ - virtual TiXmlNode *Clone() const = 0; - -protected: - TiXmlNode(NodeType _type) : TiXmlBase(), type(_type) {} - - // Copy to the allocated object. Shared functionality between Clone, Copy constructor, - // and the assignment operator. - void CopyTo(TiXmlNode *target) const; - -#ifdef TIXML_USE_STL - // The real work of the input operator. - virtual void StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) = 0; -#endif - - // Figure out what is at *p, and parse it. Returns null if it is not an xml node. - TiXmlNode *Identify(const char *start, TiXmlEncoding encoding); - - TiXmlNode *parent{nullptr}; - NodeType type; - - TiXmlNode *firstChild{nullptr}; - TiXmlNode *lastChild{nullptr}; - - TIXML_STRING value; - - TiXmlNode *prev{nullptr}; - TiXmlNode *next{nullptr}; - -private: - TiXmlNode(const TiXmlNode &); // not implemented. - void operator=(const TiXmlNode &base); // not allowed. -}; - -/** An attribute is a name-value pair. Elements have an arbitrary - number of attributes, each with a unique name. - - @note The attributes are not TiXmlNodes, since they are not - part of the tinyXML document object model. There are other - suggested ways to look at this problem. -*/ -class TiXmlAttribute : public TiXmlBase { - friend class TiXmlAttributeSet; - -public: - /// Construct an empty attribute. - TiXmlAttribute() : TiXmlBase(), document(nullptr) { prev = next = nullptr; } - -#ifdef TIXML_USE_STL - /// std::string constructor. - TiXmlAttribute(const std::string &_name, const std::string &_value) - : document(nullptr), name(_name), value(_value) - { - prev = next = nullptr; - } -#endif - - /// Construct an attribute with a name and value. - TiXmlAttribute(const char *_name, const char *_value) - : document(nullptr), name(_name), value(_value) - { - prev = next = nullptr; - } - - const char *Name() const { return name.c_str(); } ///< Return the name of this attribute. - const char *Value() const { return value.c_str(); } ///< Return the value of this attribute. - int IntValue() const; ///< Return the value of this attribute, converted to an integer. - double DoubleValue() const; ///< Return the value of this attribute, converted to a double. - - /** QueryIntValue examines the value string. It is an alternative to the - IntValue() method with richer error checking. - If the value is an integer, it is stored in 'value' and - the call returns TIXML_SUCCESS. If it is not - an integer, it returns TIXML_WRONG_TYPE. - - A specialized but useful call. Note that for success it returns 0, - which is the opposite of almost all other TinyXml calls. - */ - int QueryIntValue(int *_value) const; - /// QueryDoubleValue examines the value string. See QueryIntValue(). - int QueryDoubleValue(double *_value) const; - - void SetName(const char *_name) { name = _name; } ///< Set the name of this attribute. - void SetValue(const char *_value) { value = _value; } ///< Set the value. - - void SetIntValue(int _value); ///< Set the value from an integer. - void SetDoubleValue(double _value); ///< Set the value from a double. - -#ifdef TIXML_USE_STL - /// STL std::string form. - void SetName(const std::string &_name) - { - const StringToBuffer buf(_name); - SetName(buf.buffer ? buf.buffer : "error"); - } - /// STL std::string form. - void SetValue(const std::string &_value) - { - const StringToBuffer buf(_value); - SetValue(buf.buffer ? buf.buffer : "error"); - } -#endif - - /// Get the next sibling attribute in the DOM. Returns null at end. - const TiXmlAttribute *Next() const; - TiXmlAttribute *Next(); - /// Get the previous sibling attribute in the DOM. Returns null at beginning. - const TiXmlAttribute *Previous() const; - TiXmlAttribute *Previous(); - - bool operator==(const TiXmlAttribute &rhs) const { return rhs.name == name; } - bool operator<(const TiXmlAttribute &rhs) const { return name < rhs.name; } - bool operator>(const TiXmlAttribute &rhs) const { return name > rhs.name; } - - /* Attribute parsing starts: first letter of the name - returns: the next char after the value end quote - */ - const char *Parse(const char *p, TiXmlParsingData *data, TiXmlEncoding encoding) override; - - // Prints this Attribute to a FILE stream. - void Print(FILE *cfile, int depth) const override; - - void StreamOut(TIXML_OSTREAM *out) const override; - // [internal use] - // Set the document pointer so the attribute can report errors. - void SetDocument(TiXmlDocument *doc) { document = doc; } - -private: - TiXmlAttribute(const TiXmlAttribute &); // not implemented. - void operator=(const TiXmlAttribute &base); // not allowed. - - TiXmlDocument *document; // A pointer back to a document, for error reporting. - TIXML_STRING name; - TIXML_STRING value; - TiXmlAttribute *prev; - TiXmlAttribute *next; -}; - -/* A class used to manage a group of attributes. - It is only used internally, both by the ELEMENT and the DECLARATION. - - The set can be changed transparent to the Element and Declaration - classes that use it, but NOT transparent to the Attribute - which has to implement a next() and previous() method. Which makes - it a bit problematic and prevents the use of STL. - - This version is implemented with circular lists because: - - I like circular lists - - it demonstrates some independence from the (typical) doubly linked list. -*/ -class TiXmlAttributeSet { -public: - TiXmlAttributeSet(); - ~TiXmlAttributeSet(); - - void Add(TiXmlAttribute *attribute); - void Remove(TiXmlAttribute *attribute); - - const TiXmlAttribute *First() const - { - return (sentinel.next == &sentinel) ? nullptr : sentinel.next; - } - TiXmlAttribute *First() { return (sentinel.next == &sentinel) ? nullptr : sentinel.next; } - const TiXmlAttribute *Last() const - { - return (sentinel.prev == &sentinel) ? nullptr : sentinel.prev; - } - TiXmlAttribute *Last() { return (sentinel.prev == &sentinel) ? nullptr : sentinel.prev; } - - const TiXmlAttribute *Find(const char *name) const; - TiXmlAttribute *Find(const char *name); - - TiXmlAttributeSet(const TiXmlAttributeSet &) = delete; // not allowed - void operator=(const TiXmlAttributeSet &) = delete; // not allowed (as TiXmlAttribute) - -private: - TiXmlAttribute sentinel; -}; - -/** The element is a container class. It has a value, the element name, - and can contain other elements, text, comments, and unknowns. - Elements also contain an arbitrary number of attributes. -*/ -class TiXmlElement : public TiXmlNode { -public: - /// Construct an element. - TiXmlElement(const char *in_value); - -#ifdef TIXML_USE_STL - /// std::string constructor. - TiXmlElement(const std::string &_value); -#endif - - TiXmlElement(const TiXmlElement &); - - void operator=(const TiXmlElement &base); - - ~TiXmlElement() override; - - /** Given an attribute name, Attribute() returns the value - for the attribute of that name, or null if none exists. - */ - const char *Attribute(const char *name) const; - - /** Given an attribute name, Attribute() returns the value - for the attribute of that name, or null if none exists. - If the attribute exists and can be converted to an integer, - the integer value will be put in the return 'i', if 'i' - is non-null. - */ - const char *Attribute(const char *name, int *i) const; - - /** Given an attribute name, Attribute() returns the value - for the attribute of that name, or null if none exists. - If the attribute exists and can be converted to an double, - the double value will be put in the return 'd', if 'd' - is non-null. - */ - const char *Attribute(const char *name, double *d) const; - - /** QueryIntAttribute examines the attribute - it is an alternative to the - Attribute() method with richer error checking. - If the attribute is an integer, it is stored in 'value' and - the call returns TIXML_SUCCESS. If it is not - an integer, it returns TIXML_WRONG_TYPE. If the attribute - does not exist, then TIXML_NO_ATTRIBUTE is returned. - */ - int QueryIntAttribute(const char *name, int *_value) const; - /// QueryDoubleAttribute examines the attribute - see QueryIntAttribute(). - int QueryDoubleAttribute(const char *name, double *_value) const; - /// QueryFloatAttribute examines the attribute - see QueryIntAttribute(). - int QueryFloatAttribute(const char *name, float *_value) const - { - double d; - const int result = QueryDoubleAttribute(name, &d); - if (result == TIXML_SUCCESS) { - *_value = (float)d; - } - return result; - } - - /** Sets an attribute of name to a given value. The attribute - will be created if it does not exist, or changed if it does. - */ - void SetAttribute(const char *name, const char *_value); - -#ifdef TIXML_USE_STL - const char *Attribute(const std::string &name) const { return Attribute(name.c_str()); } - const char *Attribute(const std::string &name, int *i) const - { - return Attribute(name.c_str(), i); - } - const char *Attribute(const std::string &name, double *d) const - { - return Attribute(name.c_str(), d); - } - int QueryIntAttribute(const std::string &name, int *_value) const - { - return QueryIntAttribute(name.c_str(), _value); - } - int QueryDoubleAttribute(const std::string &name, double *_value) const - { - return QueryDoubleAttribute(name.c_str(), _value); - } - - /// STL std::string form. - void SetAttribute(const std::string &name, const std::string &_value) - { - const StringToBuffer n(name); - const StringToBuffer v(_value); - if (n.buffer && v.buffer) { - SetAttribute(n.buffer, v.buffer); - } - } - ///< STL std::string form. - void SetAttribute(const std::string &name, int _value) - { - const StringToBuffer n(name); - if (n.buffer) { - SetAttribute(n.buffer, _value); - } - } -#endif - - /** Sets an attribute of name to a given value. The attribute - will be created if it does not exist, or changed if it does. - */ - void SetAttribute(const char *name, int value); - - /** Sets an attribute of name to a given value. The attribute - will be created if it does not exist, or changed if it does. - */ - void SetDoubleAttribute(const char *name, double value); - - /** Deletes an attribute with the given name. - */ - void RemoveAttribute(const char *name); -#ifdef TIXML_USE_STL - void RemoveAttribute(const std::string &name) - { - RemoveAttribute(name.c_str()); - } ///< STL std::string form. -#endif - - const TiXmlAttribute *FirstAttribute() const - { - return attributeSet.First(); - } ///< Access the first attribute in this element. - TiXmlAttribute *FirstAttribute() { return attributeSet.First(); } - const TiXmlAttribute *LastAttribute() const - { - return attributeSet.Last(); - } ///< Access the last attribute in this element. - TiXmlAttribute *LastAttribute() { return attributeSet.Last(); } - - /** Convenience function for easy access to the text inside an element. Although easy - and concise, GetText() is limited compared to getting the TiXmlText child - and accessing it directly. - - If the first child of 'this' is a TiXmlText, the GetText() - returns the character string of the Text node, else null is returned. - - This is a convenient method for getting the text of simple contained text: - @verbatim - This is text - const char* str = fooElement->GetText(); - @endverbatim - - 'str' will be a pointer to "This is text". - - Note that this function can be misleading. If the element foo was created from - this XML: - @verbatim - This is text - @endverbatim - - then the value of str would be null. The first child node isn't a text node, it is - another element. From this XML: - @verbatim - This is text - @endverbatim - GetText() will return "This is ". - - WARNING: GetText() accesses a child node - don't become confused with the - similarly named TiXmlHandle::Text() and TiXmlNode::ToText() which are - safe type casts on the referenced node. - */ - const char *GetText() const; - - /// Creates a new Element and returns it - the returned element is a copy. - TiXmlNode *Clone() const override; - // Print the Element to a FILE stream. - void Print(FILE *cfile, int depth) const override; - - /* Attribtue parsing starts: next char past '<' - returns: next char past '>' - */ - const char *Parse(const char *p, TiXmlParsingData *data, TiXmlEncoding encoding) override; - -protected: - void CopyTo(TiXmlElement *target) const; - void ClearThis(); // like clear, but initializes 'this' object as well - -// Used to be public [internal use] -#ifdef TIXML_USE_STL - void StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) override; -#endif - void StreamOut(TIXML_OSTREAM *out) const override; - - /* [internal use] - Reads the "value" of the element -- another element, or text. - This should terminate with the current end tag. - */ - const char *ReadValue(const char *in, TiXmlParsingData *prevData, TiXmlEncoding encoding); - -private: - TiXmlAttributeSet attributeSet; -}; - -/** An XML comment. - */ -class TiXmlComment : public TiXmlNode { -public: - /// Constructs an empty comment. - TiXmlComment() : TiXmlNode(TiXmlNode::COMMENT) {} - TiXmlComment(const TiXmlComment &); - void operator=(const TiXmlComment &base); - - ~TiXmlComment() override = default; - - /// Returns a copy of this Comment. - TiXmlNode *Clone() const override; - /// Write this Comment to a FILE stream. - void Print(FILE *cfile, int depth) const override; - - /* Attribtue parsing starts: at the ! of the !-- - returns: next char past '>' - */ - const char *Parse(const char *p, TiXmlParsingData *data, TiXmlEncoding encoding) override; - -protected: - void CopyTo(TiXmlComment *target) const; - -// used to be public -#ifdef TIXML_USE_STL - void StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) override; -#endif - void StreamOut(TIXML_OSTREAM *out) const override; - -private: -}; - -/** XML text. A text node can have 2 ways to output the next. "normal" output - and CDATA. It will default to the mode it was parsed from the XML file and - you generally want to leave it alone, but you can change the output mode with - SetCDATA() and query it with CDATA(). -*/ -class TiXmlText : public TiXmlNode { - friend class TiXmlElement; - -public: - /** Constructor for text element. By default, it is treated as - normal, encoded text. If you want it be output as a CDATA text - element, set the parameter _cdata to 'true' - */ - TiXmlText(const char *initValue) : TiXmlNode(TiXmlNode::TEXT), cdata(false) - { - SetValue(initValue); - } - ~TiXmlText() override = default; - -#ifdef TIXML_USE_STL - /// Constructor. - TiXmlText(const std::string &initValue) : TiXmlNode(TiXmlNode::TEXT), cdata(false) - { - SetValue(initValue); - } -#endif - - TiXmlText(const TiXmlText ©) : TiXmlNode(TiXmlNode::TEXT), cdata(copy.cdata) - { - copy.CopyTo(this); - } - void operator=(const TiXmlText &base) { base.CopyTo(this); } - - /// Write this text object to a FILE stream. - void Print(FILE *cfile, int depth) const override; - - /// Queries whether this represents text using a CDATA section. - bool CDATA() { return cdata; } - /// Turns on or off a CDATA representation of text. - void SetCDATA(bool _cdata) { cdata = _cdata; } - - const char *Parse(const char *p, TiXmlParsingData *data, TiXmlEncoding encoding) override; - -protected: - /// [internal use] Creates a new Element and returns it. - TiXmlNode *Clone() const override; - void CopyTo(TiXmlText *target) const; - - void StreamOut(TIXML_OSTREAM *out) const override; - bool Blank() const; // returns true if all white space and new lines -// [internal use] -#ifdef TIXML_USE_STL - void StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) override; -#endif - -private: - bool cdata; // true if this should be input and output as a CDATA style text element -}; - -/** In correct XML the declaration is the first entry in the file. - @verbatim - - @endverbatim - - TinyXml will happily read or write files without a declaration, - however. There are 3 possible attributes to the declaration: - version, encoding, and standalone. - - Note: In this version of the code, the attributes are - handled as special cases, not generic attributes, simply - because there can only be at most 3 and they are always the same. -*/ -class TiXmlDeclaration : public TiXmlNode { -public: - /// Construct an empty declaration. - TiXmlDeclaration() : TiXmlNode(TiXmlNode::DECLARATION) {} - -#ifdef TIXML_USE_STL - /// Constructor. - TiXmlDeclaration(const std::string &_version, const std::string &_encoding, - const std::string &_standalone); -#endif - - /// Construct. - TiXmlDeclaration(const char *_version, const char *_encoding, const char *_standalone); - - TiXmlDeclaration(const TiXmlDeclaration ©); - void operator=(const TiXmlDeclaration ©); - - ~TiXmlDeclaration() override = default; - - /// Version. Will return an empty string if none was found. - const char *Version() const { return version.c_str(); } - /// Encoding. Will return an empty string if none was found. - const char *Encoding() const { return encoding.c_str(); } - /// Is this a standalone document? - const char *Standalone() const { return standalone.c_str(); } - - /// Creates a copy of this Declaration and returns it. - TiXmlNode *Clone() const override; - /// Print this declaration to a FILE stream. - void Print(FILE *cfile, int depth) const override; - - const char *Parse(const char *p, TiXmlParsingData *data, TiXmlEncoding encoding) override; - -protected: - void CopyTo(TiXmlDeclaration *target) const; -// used to be public -#ifdef TIXML_USE_STL - void StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) override; -#endif - void StreamOut(TIXML_OSTREAM *out) const override; - -private: - TIXML_STRING version; - TIXML_STRING encoding; - TIXML_STRING standalone; -}; - -/** Any tag that tinyXml doesn't recognize is saved as an - unknown. It is a tag of text, but should not be modified. - It will be written back to the XML, unchanged, when the file - is saved. - - DTD tags get thrown into TiXmlUnknowns. -*/ -class TiXmlUnknown : public TiXmlNode { -public: - TiXmlUnknown() : TiXmlNode(TiXmlNode::UNKNOWN) {} - ~TiXmlUnknown() override = default; - - TiXmlUnknown(const TiXmlUnknown ©) : TiXmlNode(TiXmlNode::UNKNOWN) { copy.CopyTo(this); } - void operator=(const TiXmlUnknown ©) { copy.CopyTo(this); } - - /// Creates a copy of this Unknown and returns it. - TiXmlNode *Clone() const override; - /// Print this Unknown to a FILE stream. - void Print(FILE *cfile, int depth) const override; - - const char *Parse(const char *p, TiXmlParsingData *data, TiXmlEncoding encoding) override; - -protected: - void CopyTo(TiXmlUnknown *target) const; - -#ifdef TIXML_USE_STL - void StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) override; -#endif - void StreamOut(TIXML_OSTREAM *out) const override; - -private: -}; - -/** Always the top level node. A document binds together all the - XML pieces. It can be saved, loaded, and printed to the screen. - The 'value' of a document node is the xml file name. -*/ -class TiXmlDocument : public TiXmlNode { -public: - /// Create an empty document, that has no name. - TiXmlDocument(); - /// Create a document with a name. The name of the document is also the filename of the xml. - TiXmlDocument(const char *documentName); - -#ifdef TIXML_USE_STL - /// Constructor. - TiXmlDocument(const std::string &documentName); -#endif - - TiXmlDocument(const TiXmlDocument ©); - void operator=(const TiXmlDocument ©); - - ~TiXmlDocument() override = default; - - /** Load a file using the current document value. - Returns true if successful. Will delete any existing - document data before loading. - */ - bool LoadFile(TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING); - /// Save a file using the current document value. Returns true if successful. - bool SaveFile() const; - /// Load a file using the given filename. Returns true if successful. - bool LoadFile(const char *filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING); - /// Save a file using the given filename. Returns true if successful. - bool SaveFile(const char *filename) const; - -#ifdef TIXML_USE_STL - bool LoadFile(const std::string &filename, - TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING) ///< STL std::string version. - { - const StringToBuffer f(filename); - return (f.buffer && LoadFile(f.buffer, encoding)); - } - bool SaveFile(const std::string &filename) const ///< STL std::string version. - { - const StringToBuffer f(filename); - return (f.buffer && SaveFile(f.buffer)); - } -#endif - - /** Parse the given null terminated block of xml data. Passing in an encoding to this - method (either TIXML_ENCODING_LEGACY or TIXML_ENCODING_UTF8 will force TinyXml - to use that encoding, regardless of what TinyXml might otherwise try to detect. - */ - const char *Parse(const char *p, TiXmlParsingData *data = nullptr, - TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING) override; - - /** Get the root element -- the only top level element -- of the document. - In well formed XML, there should only be one. TinyXml is tolerant of - multiple elements at the document level. - */ - const TiXmlElement *RootElement() const { return FirstChildElement(); } - TiXmlElement *RootElement() { return FirstChildElement(); } - - /** If an error occurs, Error will be set to true. Also, - - The ErrorId() will contain the integer identifier of the error (not generally useful) - - The ErrorDesc() method will return the name of the error. (very useful) - - The ErrorRow() and ErrorCol() will return the location of the error (if known) - */ - bool Error() const { return error; } - - /// Contains a textual (english) description of the error if one occurs. - const char *ErrorDesc() const { return errorDesc.c_str(); } - - /** Generally, you probably want the error string ( ErrorDesc() ). But if you - prefer the ErrorId, this function will fetch it. - */ - int ErrorId() const { return errorId; } - - /** Returns the location (if known) of the error. The first column is column 1, - and the first row is row 1. A value of 0 means the row and column wasn't applicable - (memory errors, for example, have no row/column) or the parser lost the error. (An - error in the error reporting, in that case.) - - @sa SetTabSize, Row, Column - */ - int ErrorRow() { return errorLocation.row + 1; } - int ErrorCol() - { - return errorLocation.col + 1; - } ///< The column where the error occured. See ErrorRow() - - /** SetTabSize() allows the error reporting functions (ErrorRow() and ErrorCol()) - to report the correct values for row and column. It does not change the output - or input in any way. - - By calling this method, with a tab size - greater than 0, the row and column of each node and attribute is stored - when the file is loaded. Very useful for tracking the DOM back in to - the source file. - - The tab size is required for calculating the location of nodes. If not - set, the default of 4 is used. The tabsize is set per document. Setting - the tabsize to 0 disables row/column tracking. - - Note that row and column tracking is not supported when using operator>>. - - The tab size needs to be enabled before the parse or load. Correct usage: - @verbatim - TiXmlDocument doc; - doc.SetTabSize( 8 ); - doc.Load( "myfile.xml" ); - @endverbatim - - @sa Row, Column - */ - void SetTabSize(int _tabsize) { tabsize = _tabsize; } - - int TabSize() const { return tabsize; } - - /** If you have handled the error, it can be reset with this call. The error - state is automatically cleared if you Parse a new XML block. - */ - void ClearError() - { - error = false; - errorId = 0; - errorDesc = ""; - errorLocation.row = errorLocation.col = 0; - // errorLocation.last = 0; - } - - /** Dump the document to standard out. */ - void Print() const { Print(stdout, 0); } - - /// Print this Document to a FILE stream. - void Print(FILE *cfile, int depth = 0) const override; - // [internal use] - void SetError(int err, const char *errorLocation, TiXmlParsingData *prevData, - TiXmlEncoding encoding); - -protected: - void StreamOut(TIXML_OSTREAM *out) const override; - // [internal use] - TiXmlNode *Clone() const override; -#ifdef TIXML_USE_STL - void StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) override; -#endif - -private: - void CopyTo(TiXmlDocument *target) const; - - bool error{false}; - int errorId{0}; - TIXML_STRING errorDesc; - int tabsize{4}; - TiXmlCursor errorLocation; - bool useMicrosoftBOM{false}; // the UTF-8 BOM were found when read. Note this, and try to write. -}; - -/** - A TiXmlHandle is a class that wraps a node pointer with null checks; this is - an incredibly useful thing. Note that TiXmlHandle is not part of the TinyXml - DOM structure. It is a separate utility class. - - Take an example: - @verbatim - - - - - - - @endverbatim - - Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very - easy to write a *lot* of code that looks like: - - @verbatim - TiXmlElement* root = document.FirstChildElement( "Document" ); - if ( root ) - { - TiXmlElement* element = root->FirstChildElement( "Element" ); - if ( element ) - { - TiXmlElement* child = element->FirstChildElement( "Child" ); - if ( child ) - { - TiXmlElement* child2 = child->NextSiblingElement( "Child" ); - if ( child2 ) - { - // Finally do something useful. - @endverbatim - - And that doesn't even cover "else" cases. TiXmlHandle addresses the verbosity - of such code. A TiXmlHandle checks for null pointers so it is perfectly safe - and correct to use: - - @verbatim - TiXmlHandle docHandle( &document ); - TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( - "Child", 1 ).Element(); if ( child2 ) - { - // do something useful - @endverbatim - - Which is MUCH more concise and useful. - - It is also safe to copy handles - internally they are nothing more than node pointers. - @verbatim - TiXmlHandle handleCopy = handle; - @endverbatim - - What they should not be used for is iteration: - - @verbatim - int i=0; - while ( true ) - { - TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" - ).Child( "Child", i ).Element(); if ( !child ) break; - // do something - ++i; - } - @endverbatim - - It seems reasonable, but it is in fact two embedded while loops. The Child method is - a linear walk to find the element, so this code would iterate much more than it needs - to. Instead, prefer: - - @verbatim - TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" - ).FirstChild( "Child" ).Element(); - - for( child; child; child=child->NextSiblingElement() ) - { - // do something - } - @endverbatim -*/ -class TiXmlHandle { -public: - /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. - TiXmlHandle(TiXmlNode *_node) : node(_node) {} - /// Copy constructor - TiXmlHandle(const TiXmlHandle &ref) = default; - TiXmlHandle operator=(const TiXmlHandle &ref) - { - this->node = ref.node; - return *this; - } - - /// Return a handle to the first child node. - TiXmlHandle FirstChild() const; - /// Return a handle to the first child node with the given name. - TiXmlHandle FirstChild(const char *value) const; - /// Return a handle to the first child element. - TiXmlHandle FirstChildElement() const; - /// Return a handle to the first child element with the given name. - TiXmlHandle FirstChildElement(const char *value) const; - - /** Return a handle to the "index" child with the given name. - The first child is 0, the second 1, etc. - */ - TiXmlHandle Child(const char *value, int index) const; - /** Return a handle to the "index" child. - The first child is 0, the second 1, etc. - */ - TiXmlHandle Child(int index) const; - /** Return a handle to the "index" child element with the given name. - The first child element is 0, the second 1, etc. Note that only TiXmlElements - are indexed: other types are not counted. - */ - TiXmlHandle ChildElement(const char *value, int index) const; - /** Return a handle to the "index" child element. - The first child element is 0, the second 1, etc. Note that only TiXmlElements - are indexed: other types are not counted. - */ - TiXmlHandle ChildElement(int index) const; - -#ifdef TIXML_USE_STL - TiXmlHandle FirstChild(const std::string &_value) const { return FirstChild(_value.c_str()); } - TiXmlHandle FirstChildElement(const std::string &_value) const - { - return FirstChildElement(_value.c_str()); - } - - TiXmlHandle Child(const std::string &_value, int index) const - { - return Child(_value.c_str(), index); - } - TiXmlHandle ChildElement(const std::string &_value, int index) const - { - return ChildElement(_value.c_str(), index); - } -#endif - - /// Return the handle as a TiXmlNode. This may return null. - TiXmlNode *Node() const { return node; } - /// Return the handle as a TiXmlElement. This may return null. - TiXmlElement *Element() const - { - return ((node && node->ToElement()) ? node->ToElement() : nullptr); - } - /// Return the handle as a TiXmlText. This may return null. - TiXmlText *Text() const { return ((node && node->ToText()) ? node->ToText() : nullptr); } - /// Return the handle as a TiXmlUnknown. This may return null; - TiXmlUnknown *Unknown() const - { - return ((node && node->ToUnknown()) ? node->ToUnknown() : nullptr); - } - -private: - TiXmlNode *node; -}; - -inline const TiXmlDocument *TiXmlNode::ToDocument() const -{ - return (type == DOCUMENT) ? dynamic_cast(this) : nullptr; -} -inline const TiXmlElement *TiXmlNode::ToElement() const -{ - return (type == ELEMENT) ? dynamic_cast(this) : nullptr; -} -inline const TiXmlComment *TiXmlNode::ToComment() const -{ - return (type == COMMENT) ? dynamic_cast(this) : nullptr; -} -inline const TiXmlUnknown *TiXmlNode::ToUnknown() const -{ - return (type == UNKNOWN) ? dynamic_cast(this) : nullptr; -} -inline const TiXmlText *TiXmlNode::ToText() const -{ - return (type == TEXT) ? dynamic_cast(this) : nullptr; -} -inline const TiXmlDeclaration *TiXmlNode::ToDeclaration() const -{ - return (type == DECLARATION) ? dynamic_cast(this) : nullptr; -} - -inline TiXmlDocument *TiXmlNode::ToDocument() -{ - return (type == DOCUMENT) ? dynamic_cast(this) : nullptr; -} -inline TiXmlElement *TiXmlNode::ToElement() -{ - return (type == ELEMENT) ? dynamic_cast(this) : nullptr; -} -inline TiXmlComment *TiXmlNode::ToComment() -{ - return (type == COMMENT) ? dynamic_cast(this) : nullptr; -} -inline TiXmlUnknown *TiXmlNode::ToUnknown() -{ - return (type == UNKNOWN) ? dynamic_cast(this) : nullptr; -} -inline TiXmlText *TiXmlNode::ToText() -{ - return (type == TEXT) ? dynamic_cast(this) : nullptr; -} -inline TiXmlDeclaration *TiXmlNode::ToDeclaration() -{ - return (type == DECLARATION) ? dynamic_cast(this) : nullptr; -} - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#endif diff --git a/src/core/tinyxmlerror.cc b/src/core/tinyxmlerror.cc deleted file mode 100644 index 1c1466b011..0000000000 --- a/src/core/tinyxmlerror.cc +++ /dev/null @@ -1,51 +0,0 @@ -/* -www.sourceforge.net/projects/tinyxml -Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#include "tinyxml.h" - -// The goal of the seperate error file is to make the first -// step towards localization. tinyxml (currently) only supports -// english error messages, but the could now be translated. -// -// It also cleans up the code a bit. -// - -const char *TiXmlBase::errorString[TIXML_ERROR_STRING_COUNT] = { - "No error", - "Error", - "Failed to open file", - "Memory allocation failed.", - "Error parsing Element.", - "Failed to read Element name", - "Error reading Element value.", - "Error reading Attributes.", - "Error: empty tag.", - "Error reading end tag.", - "Error parsing Unknown.", - "Error parsing Comment.", - "Error parsing Declaration.", - "Error document empty.", - "Error null (0) or unexpected EOF found in input stream.", - "Error parsing CDATA.", -}; diff --git a/src/core/tinyxmlparser.cc b/src/core/tinyxmlparser.cc deleted file mode 100644 index 7c0c11e137..0000000000 --- a/src/core/tinyxmlparser.cc +++ /dev/null @@ -1,1511 +0,0 @@ -/* -www.sourceforge.net/projects/tinyxml -Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#include -#include - -#include "tinyxml.h" - -// #define DEBUG_PARSER - -// Note tha "PutString" hardcodes the same list. This -// is less flexible than it appears. Changing the entries -// or order will break putstring. -TiXmlBase::Entity TiXmlBase::entity[NUM_ENTITY] = {{"&", 5, '&'}, - {"<", 4, '<'}, - {">", 4, '>'}, - {""", 6, '\"'}, - {"'", 6, '\''}}; - -// Bunch of unicode info at: -// http://www.unicode.org/faq/utf_bom.html -// Including the basic of this table, which determines the #bytes in the -// sequence from the lead byte. 1 placed for invalid sequences -- -// although the result will be junk, pass it through as much as possible. -// Beware of the non-characters in UTF-8: -// ef bb bf (Microsoft "lead bytes") -// ef bf be -// ef bf bf - -const unsigned char TIXML_UTF_LEAD_0 = 0xefU; -const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; -const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; - -const int TiXmlBase::utf8ByteTable[256] = { - // 0 1 2 3 4 5 6 7 8 9 a b - // c d e f - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 End of ASCII range - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 0x80 to 0xc1 invalid - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0 - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0 - 1, 1, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 0xc2 to 0xdf 2 byte - 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 - 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 0xe0 to 0xef 3 byte - 4, 4, 4, 4, 4, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid -}; - -void TiXmlBase::ConvertUTF32ToUTF8(unsigned long input, char *output, int *length) -{ - const unsigned long BYTE_MASK = 0xBF; - const unsigned long BYTE_MARK = 0x80; - const unsigned long FIRST_BYTE_MARK[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC}; - - if (input < 0x80) { - *length = 1; - } - else if (input < 0x800) { - *length = 2; - } - else if (input < 0x10000) { - *length = 3; - } - else if (input < 0x200000) { - *length = 4; - } - else { - *length = 0; - return; - } // This code won't covert this correctly anyway. - - output += *length; - - // Scary scary fall throughs. - switch (*length) { - case 4: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); - input >>= 6; - case 3: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); - input >>= 6; - case 2: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); - input >>= 6; - case 1: - --output; - *output = (char)(input | FIRST_BYTE_MARK[*length]); - } -} - -/*static*/ int TiXmlBase::IsAlpha(unsigned char anyByte, TiXmlEncoding /*encoding*/) -{ - // This will only work for low-ascii, everything else is assumed to be a valid - // letter. I'm not sure this is the best approach, but it is quite tricky trying - // to figure out alhabetical vs. not across encoding. So take a very - // conservative approach. - - // if ( encoding == TIXML_ENCODING_UTF8 ) - // { - if (anyByte < 127) { - return isalpha(anyByte); - } - else { - return 1; // What else to do? The unicode set is huge...get the english ones right. - } - // } - // else - // { - // return isalpha( anyByte ); - // } -} - -/*static*/ int TiXmlBase::IsAlphaNum(unsigned char anyByte, TiXmlEncoding /*encoding*/) -{ - // This will only work for low-ascii, everything else is assumed to be a valid - // letter. I'm not sure this is the best approach, but it is quite tricky trying - // to figure out alhabetical vs. not across encoding. So take a very - // conservative approach. - - // if ( encoding == TIXML_ENCODING_UTF8 ) - // { - if (anyByte < 127) { - return isalnum(anyByte); - } - else { - return 1; // What else to do? The unicode set is huge...get the english ones right. - } - // } - // else - // { - // return isalnum( anyByte ); - // } -} - -class TiXmlParsingData { - friend class TiXmlDocument; - -public: - void Stamp(const char *now, TiXmlEncoding encoding); - - const TiXmlCursor &Cursor() { return cursor; } - -private: - // Only used by the document! - TiXmlParsingData(const char *start, int _tabsize, int row, int col) - { - assert(start); - stamp = start; - tabsize = _tabsize; - cursor.row = row; - cursor.col = col; - } - - TiXmlCursor cursor; - const char *stamp; - int tabsize; -}; - -void TiXmlParsingData::Stamp(const char *now, TiXmlEncoding encoding) -{ - assert(now); - - // Do nothing if the tabsize is 0. - if (tabsize < 1) { - return; - } - - // Get the current row, column. - int row = cursor.row; - int col = cursor.col; - const char *p = stamp; - assert(p); - - while (p < now) { - // Treat p as unsigned, so we have a happy compiler. - const auto *pU = reinterpret_cast(p); - - // Code contributed by Fletcher Dunn: (modified by lee) - switch (*pU) { - case 0: - // We *should* never get here, but in case we do, don't - // advance past the terminating null character, ever - return; - - case '\r': - // bump down to the next line - ++row; - col = 0; - // Eat the character - ++p; - - // Check for \r\n sequence, and treat this as a single character - if (*p == '\n') { - ++p; - } - break; - - case '\n': - // bump down to the next line - ++row; - col = 0; - - // Eat the character - ++p; - - // Check for \n\r sequence, and treat this as a single - // character. (Yes, this bizarre thing does occur still - // on some arcane platforms...) - if (*p == '\r') { - ++p; - } - break; - - case '\t': - // Eat the character - ++p; - - // Skip to next tab stop - col = (col / tabsize + 1) * tabsize; - break; - - case TIXML_UTF_LEAD_0: - if (encoding == TIXML_ENCODING_UTF8) { - if (*(p + 1) && *(p + 2)) { - // In these cases, don't advance the column. These are - // 0-width spaces. - if (*(pU + 1) == TIXML_UTF_LEAD_1 && *(pU + 2) == TIXML_UTF_LEAD_2) { - p += 3; - } - else if (*(pU + 1) == 0xbfU && *(pU + 2) == 0xbeU) { - p += 3; - } - else if (*(pU + 1) == 0xbfU && *(pU + 2) == 0xbfU) { - p += 3; - } - else { - p += 3; - ++col; - } // A normal character. - } - } - else { - ++p; - ++col; - } - break; - - default: - if (encoding == TIXML_ENCODING_UTF8) { - // Eat the 1 to 4 byte utf8 character. - int step = TiXmlBase::utf8ByteTable[*reinterpret_cast(p)]; - if (step == 0) { - step = 1; // Error case from bad encoding, but handle gracefully. - } - p += step; - - // Just advance one column, of course. - ++col; - } - else { - ++p; - ++col; - } - break; - } - } - cursor.row = row; - cursor.col = col; - assert(cursor.row >= -1); - assert(cursor.col >= -1); - stamp = p; - assert(stamp); -} - -const char *TiXmlBase::SkipWhiteSpace(const char *p, TiXmlEncoding encoding) -{ - if (!p || !*p) { - return nullptr; - } - if (encoding == TIXML_ENCODING_UTF8) { - while (*p) { - const auto *pU = reinterpret_cast(p); - - // Skip the stupid Microsoft UTF-8 Byte order marks - if (*(pU + 0) == TIXML_UTF_LEAD_0 && *(pU + 1) == TIXML_UTF_LEAD_1 && - *(pU + 2) == TIXML_UTF_LEAD_2) { - p += 3; - continue; - } - else if (*(pU + 0) == TIXML_UTF_LEAD_0 && *(pU + 1) == 0xbfU && *(pU + 2) == 0xbeU) { - p += 3; - continue; - } - else if (*(pU + 0) == TIXML_UTF_LEAD_0 && *(pU + 1) == 0xbfU && *(pU + 2) == 0xbfU) { - p += 3; - continue; - } - - if (IsWhiteSpace(*p) || *p == '\n' || *p == '\r') { // Still using old rules for white space. - ++p; - } - else { - break; - } - } - } - else { - while (*p && (IsWhiteSpace(*p) || *p == '\n' || *p == '\r')) { - ++p; - } - } - - return p; -} - -#ifdef TIXML_USE_STL -/*static*/ bool TiXmlBase::StreamWhiteSpace(TIXML_ISTREAM *in, TIXML_STRING *tag) -{ - for (;;) { - if (!in->good()) { - return false; - } - - const int c = in->peek(); - // At this scope, we can't get to a document. So fail silently. - if (!IsWhiteSpace(c) || c <= 0) { - return true; - } - - *tag += (char)in->get(); - } -} - -/*static*/ bool TiXmlBase::StreamTo(TIXML_ISTREAM *in, int character, TIXML_STRING *tag) -{ - // assert( character > 0 && character < 128 ); // else it won't work in utf-8 - while (in->good()) { - const int c = in->peek(); - if (c == character) { - return true; - } - if (c <= 0) { // Silent failure: can't get document at this scope - return false; - } - - in->get(); - *tag += (char)c; - } - return false; -} -#endif - -const char *TiXmlBase::ReadName(const char *p, TIXML_STRING *name, TiXmlEncoding encoding) -{ - *name = ""; - assert(p); - - // Names start with letters or underscores. - // Of course, in unicode, tinyxml has no idea what a letter *is*. The - // algorithm is generous. - // - // After that, they can be letters, underscores, numbers, - // hyphens, or colons. (Colons are valid ony for namespaces, - // but tinyxml can't tell namespaces from names.) - if (p && *p && (IsAlpha((unsigned char)*p, encoding) || *p == '_')) { - while (p && *p && - (IsAlphaNum((unsigned char)*p, encoding) || *p == '_' || *p == '-' || *p == '.' || - *p == ':')) { - (*name) += *p; - ++p; - } - return p; - } - return nullptr; -} - -const char *TiXmlBase::GetEntity(const char *p, char *value, int *length, TiXmlEncoding encoding) -{ - // Presume an entity, and pull it out. - const TIXML_STRING ent; - int i; - *length = 0; - - if (*(p + 1) && *(p + 1) == '#' && *(p + 2)) { - unsigned long ucs = 0; - ptrdiff_t delta = 0; - unsigned mult = 1; - - if (*(p + 2) == 'x') { - // Hexadecimal. - if (!*(p + 3)) { - return nullptr; - } - - const char *q = p + 3; - q = strchr(q, ';'); - - if (!q || !*q) { - return nullptr; - } - - delta = q - p; - --q; - - while (*q != 'x') { - if (*q >= '0' && *q <= '9') { - ucs += mult * (*q - '0'); - } - else if (*q >= 'a' && *q <= 'f') { - ucs += mult * (*q - 'a' + 10); - } - else if (*q >= 'A' && *q <= 'F') { - ucs += mult * (*q - 'A' + 10); - } - else { - return nullptr; - } - mult *= 16; - --q; - } - } - else { - // Decimal. - if (!*(p + 2)) { - return nullptr; - } - - const char *q = p + 2; - q = strchr(q, ';'); - - if (!q || !*q) { - return nullptr; - } - - delta = q - p; - --q; - - while (*q != '#') { - if (*q >= '0' && *q <= '9') { - ucs += mult * (*q - '0'); - } - else { - return nullptr; - } - mult *= 10; - --q; - } - } - if (encoding == TIXML_ENCODING_UTF8) { - // convert the UCS to UTF-8 - ConvertUTF32ToUTF8(ucs, value, length); - } - else { - *value = (char)ucs; - *length = 1; - } - return p + delta + 1; - } - - // Now try to match it. - for (i = 0; i < NUM_ENTITY; ++i) { - if (strncmp(entity[i].str, p, entity[i].strLength) == 0) { - assert(strlen(entity[i].str) == entity[i].strLength); - *value = entity[i].chr; - *length = 1; - return (p + entity[i].strLength); - } - } - - // So it wasn't an entity, its unrecognized, or something like that. - *value = *p; // Don't put back the last one, since we return it! - return p + 1; -} - -bool TiXmlBase::StringEqual(const char *p, const char *tag, bool ignoreCase, - TiXmlEncoding encoding) -{ - assert(p); - assert(tag); - if (!p || !*p) { - assert(0); - return false; - } - - const char *q = p; - - if (ignoreCase) { - while (*q && *tag && ToLower(*q, encoding) == ToLower(*tag, encoding)) { - ++q; - ++tag; - } - - if (*tag == 0) { - return true; - } - } - else { - while (*q && *tag && *q == *tag) { - ++q; - ++tag; - } - - if (*tag == 0) { // Have we found the end of the tag, and everything equal? - return true; - } - } - return false; -} - -const char *TiXmlBase::ReadText(const char *p, TIXML_STRING *text, bool trimWhiteSpace, - const char *endTag, bool caseInsensitive, TiXmlEncoding encoding) -{ - *text = ""; - if (!trimWhiteSpace // certain tags always keep whitespace - || !condenseWhiteSpace) // if true, whitespace is always kept - { - // Keep all the white space. - while (p && *p && !StringEqual(p, endTag, caseInsensitive, encoding)) { - int len; - char cArr[4] = {0, 0, 0, 0}; - p = GetChar(p, cArr, &len, encoding); - text->append(cArr, len); - } - } - else { - bool whitespace = false; - - // Remove leading white space: - p = SkipWhiteSpace(p, encoding); - while (p && *p && !StringEqual(p, endTag, caseInsensitive, encoding)) { - if (*p == '\r' || *p == '\n') { - whitespace = true; - ++p; - } - else if (IsWhiteSpace(*p)) { - whitespace = true; - ++p; - } - else { - // If we've found whitespace, add it before the - // new character. Any whitespace just becomes a space. - if (whitespace) { - (*text) += ' '; - whitespace = false; - } - int len; - char cArr[4] = {0, 0, 0, 0}; - p = GetChar(p, cArr, &len, encoding); - if (len == 1) { - (*text) += cArr[0]; // more efficient - } - else { - text->append(cArr, len); - } - } - } - } - return p + strlen(endTag); -} - -#ifdef TIXML_USE_STL - -void TiXmlDocument::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) -{ - // The basic issue with a document is that we don't know what we're - // streaming. Read something presumed to be a tag (and hope), then - // identify it, and call the appropriate stream method on the tag. - // - // This "pre-streaming" will never read the closing ">" so the - // sub-tag can orient itself. - - if (!StreamTo(in, '<', tag)) { - SetError(TIXML_ERROR_PARSING_EMPTY, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); - return; - } - - while (in->good()) { - const int tagIndex = (int)tag->length(); - while (in->good() && in->peek() != '>') { - const int c = in->get(); - if (c <= 0) { - SetError(TIXML_ERROR_EMBEDDED_NULL, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); - break; - } - (*tag) += (char)c; - } - - if (in->good()) { - // We now have something we presume to be a node of - // some sort. Identify it, and call the node to - // continue streaming. - TiXmlNode *node = Identify(tag->c_str() + tagIndex, TIXML_DEFAULT_ENCODING); - - if (node) { - node->StreamIn(in, tag); - const bool isElement = node->ToElement() != nullptr; - delete node; - node = nullptr; - - // If this is the root element, we're done. Parsing will be - // done by the >> operator. - if (isElement) { - return; - } - } - else { - SetError(TIXML_ERROR, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); - return; - } - } - } - // We should have returned sooner. - SetError(TIXML_ERROR, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); -} - -#endif - -const char *TiXmlDocument::Parse(const char *p, TiXmlParsingData *prevData, TiXmlEncoding encoding) -{ - ClearError(); - - // Parse away, at the document level. Since a document - // contains nothing but other tags, most of what happens - // here is skipping white space. - if (!p || !*p) { - SetError(TIXML_ERROR_DOCUMENT_EMPTY, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); - return nullptr; - } - - // Note that, for a document, this needs to come - // before the while space skip, so that parsing - // starts from the pointer we are given. - location.Clear(); - if (prevData) { - location.row = prevData->cursor.row; - location.col = prevData->cursor.col; - } - else { - location.row = 0; - location.col = 0; - } - TiXmlParsingData data(p, TabSize(), location.row, location.col); - location = data.Cursor(); - - if (encoding == TIXML_ENCODING_UNKNOWN) { - // Check for the Microsoft UTF-8 lead bytes. - const auto *pU = reinterpret_cast(p); - if (*(pU + 0) && *(pU + 0) == TIXML_UTF_LEAD_0 && *(pU + 1) && *(pU + 1) == TIXML_UTF_LEAD_1 && - *(pU + 2) && *(pU + 2) == TIXML_UTF_LEAD_2) { - encoding = TIXML_ENCODING_UTF8; - useMicrosoftBOM = true; - } - } - - p = SkipWhiteSpace(p, encoding); - if (!p) { - SetError(TIXML_ERROR_DOCUMENT_EMPTY, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); - return nullptr; - } - - while (p && *p) { - TiXmlNode *node = Identify(p, encoding); - if (node) { - p = node->Parse(p, &data, encoding); - LinkEndChild(node); - } - else { - break; - } - - // Did we get encoding info? - if (encoding == TIXML_ENCODING_UNKNOWN && node->ToDeclaration()) { - const TiXmlDeclaration *dec = node->ToDeclaration(); - const char *enc = dec->Encoding(); - assert(enc); - - if (*enc == 0) { - encoding = TIXML_ENCODING_UTF8; - } - else if (StringEqual(enc, "UTF-8", true, TIXML_ENCODING_UNKNOWN)) { - encoding = TIXML_ENCODING_UTF8; - } - else if (StringEqual(enc, "UTF8", true, TIXML_ENCODING_UNKNOWN)) { - encoding = TIXML_ENCODING_UTF8; // incorrect, but be nice - } - else { - encoding = TIXML_ENCODING_LEGACY; - } - } - - p = SkipWhiteSpace(p, encoding); - } - - // Was this empty? - if (!firstChild) { - SetError(TIXML_ERROR_DOCUMENT_EMPTY, nullptr, nullptr, encoding); - return nullptr; - } - - // All is well. - return p; -} - -void TiXmlDocument::SetError(int err, const char *pError, TiXmlParsingData *data, - TiXmlEncoding encoding) -{ - // The first error in a chain is more accurate - don't set again! - if (error) { - return; - } - - assert(err > 0 && err < TIXML_ERROR_STRING_COUNT); - error = true; - errorId = err; - errorDesc = errorString[errorId]; - - errorLocation.Clear(); - if (pError && data) { - data->Stamp(pError, encoding); - errorLocation = data->Cursor(); - } -} - -TiXmlNode *TiXmlNode::Identify(const char *p, TiXmlEncoding encoding) -{ - TiXmlNode *returnNode = nullptr; - - p = SkipWhiteSpace(p, encoding); - if (!p || !*p || *p != '<') { - return nullptr; - } - - TiXmlDocument *doc = GetDocument(); - p = SkipWhiteSpace(p, encoding); - - if (!p || !*p) { - return nullptr; - } - - // What is this thing? - // - Elements start with a letter or underscore, but xml is reserved. - // - Comments: "; - - if (!StringEqual(p, startTag, false, encoding)) { - document->SetError(TIXML_ERROR_PARSING_COMMENT, p, data, encoding); - return nullptr; - } - p += strlen(startTag); - p = ReadText(p, &value, false, endTag, false, encoding); - return p; -} - -const char *TiXmlAttribute::Parse(const char *p, TiXmlParsingData *data, TiXmlEncoding encoding) -{ - p = SkipWhiteSpace(p, encoding); - if (!p || !*p) { - return nullptr; - } - - // int tabsize = 4; - // if ( document ) { - // tabsize = document->TabSize(); - // } - - if (data) { - data->Stamp(p, encoding); - location = data->Cursor(); - } - // Read the name, the '=' and the value. - const char *pErr = p; - p = ReadName(p, &name, encoding); - if (!p || !*p) { - if (document) { - document->SetError(TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding); - } - return nullptr; - } - p = SkipWhiteSpace(p, encoding); - if (!p || !*p || *p != '=') { - if (document) { - document->SetError(TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding); - } - return nullptr; - } - - ++p; // skip '=' - p = SkipWhiteSpace(p, encoding); - if (!p || !*p) { - if (document) { - document->SetError(TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding); - } - return nullptr; - } - - const char *end; - - if (*p == '\'') { - ++p; - end = "\'"; - p = ReadText(p, &value, false, end, false, encoding); - } - else if (*p == '"') { - ++p; - end = "\""; - p = ReadText(p, &value, false, end, false, encoding); - } - else { - // All attribute values should be in single or double quotes. - // But this is such a common error that the parser will try - // its best, even without them. - value = ""; - while (p && *p // existence - && !IsWhiteSpace(*p) && *p != '\n' && *p != '\r' // whitespace - && *p != '/' && *p != '>') // tag end - { - value += *p; - ++p; - } - } - return p; -} - -#ifdef TIXML_USE_STL -void TiXmlText::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) -{ - if (cdata) { - const int c = in->get(); - if (c <= 0) { - TiXmlDocument *document = GetDocument(); - if (document) { - document->SetError(TIXML_ERROR_EMBEDDED_NULL, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); - } - return; - } - - (*tag) += (char)c; - - if (c == '>' && tag->at(tag->length() - 2) == ']' && tag->at(tag->length() - 3) == ']') { - // All is well. - return; - } - } - else { - while (in->good()) { - const int c = in->peek(); - if (c == '<') { - return; - } - if (c <= 0) { - TiXmlDocument *document = GetDocument(); - if (document) { - document->SetError(TIXML_ERROR_EMBEDDED_NULL, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); - } - return; - } - - (*tag) += (char)c; - in->get(); - } - } -} -#endif - -const char *TiXmlText::Parse(const char *p, TiXmlParsingData *data, TiXmlEncoding encoding) -{ - value = ""; - TiXmlDocument *document = GetDocument(); - - if (data) { - data->Stamp(p, encoding); - location = data->Cursor(); - } - - const char *const startTag = ""; - - if (cdata || StringEqual(p, startTag, false, encoding)) { - cdata = true; - - if (!StringEqual(p, startTag, false, encoding)) { - document->SetError(TIXML_ERROR_PARSING_CDATA, p, data, encoding); - return nullptr; - } - p += strlen(startTag); - - // Keep all the white space, ignore the encoding, etc. - while (p && *p && !StringEqual(p, endTag, false, encoding)) { - value += *p; - ++p; - } - - TIXML_STRING dummy; - p = ReadText(p, &dummy, false, endTag, false, encoding); - return p; - } - else { - const bool ignoreWhite = true; - - const char *end = "<"; - p = ReadText(p, &value, ignoreWhite, end, false, encoding); - if (p) { - return p - 1; // don't truncate the '<' - } - return nullptr; - } -} - -#ifdef TIXML_USE_STL -void TiXmlDeclaration::StreamIn(TIXML_ISTREAM *in, TIXML_STRING *tag) -{ - while (in->good()) { - const int c = in->get(); - if (c <= 0) { - TiXmlDocument *document = GetDocument(); - if (document) { - document->SetError(TIXML_ERROR_EMBEDDED_NULL, nullptr, nullptr, TIXML_ENCODING_UNKNOWN); - } - return; - } - (*tag) += (char)c; - - if (c == '>') { - // All is well. - return; - } - } -} -#endif - -const char *TiXmlDeclaration::Parse(const char *p, TiXmlParsingData *data, TiXmlEncoding _encoding) -{ - p = SkipWhiteSpace(p, _encoding); - // Find the beginning, find the end, and look for - // the stuff in-between. - TiXmlDocument *document = GetDocument(); - if (!p || !*p || !StringEqual(p, "SetError(TIXML_ERROR_PARSING_DECLARATION, nullptr, nullptr, _encoding); - } - return nullptr; - } - if (data) { - data->Stamp(p, _encoding); - location = data->Cursor(); - } - p += 5; - - version = ""; - encoding = ""; - standalone = ""; - - while (p && *p) { - if (*p == '>') { - ++p; - return p; - } - - p = SkipWhiteSpace(p, _encoding); - if (StringEqual(p, "version", true, _encoding)) { - TiXmlAttribute attrib; - p = attrib.Parse(p, data, _encoding); - version = attrib.Value(); - } - else if (StringEqual(p, "encoding", true, _encoding)) { - TiXmlAttribute attrib; - p = attrib.Parse(p, data, _encoding); - encoding = attrib.Value(); - } - else if (StringEqual(p, "standalone", true, _encoding)) { - TiXmlAttribute attrib; - p = attrib.Parse(p, data, _encoding); - standalone = attrib.Value(); - } - else { - // Read over whatever it is. - while (p && *p && *p != '>' && !IsWhiteSpace(*p)) { - ++p; - } - } - } - return nullptr; -} - -bool TiXmlText::Blank() const -{ - for (const char c : value) { - if (!IsWhiteSpace(c)) { - return false; - } - } - return true; -} diff --git a/src/games/agg/agg.cc b/src/games/agg/agg.cc index f5f1a2ba85..91e495c88c 100644 --- a/src/games/agg/agg.cc +++ b/src/games/agg/agg.cc @@ -152,6 +152,10 @@ std::shared_ptr AGG::makeAGG(istream &in) if (!in.good()) { throw std::runtime_error("Error reading the number of function nodes"); } + if (n <= 0 || S < 0 || P < 0) { + throw std::runtime_error("Error in game file: invalid AGG header (number of players, " + "action nodes, or function nodes out of range)"); + } stripComment(in); // enter sizes of action sets: diff --git a/src/games/agg/bagg.cc b/src/games/agg/bagg.cc index ab58e63b6d..12d36de06a 100644 --- a/src/games/agg/bagg.cc +++ b/src/games/agg/bagg.cc @@ -73,18 +73,22 @@ std::shared_ptr BAGG::makeBAGG(istream &in) in >> S; stripComment(in); in >> P; + if (!in || N <= 0 || S < 0 || P < 0) { + throw std::runtime_error("Error in game file: expected BAGG header with number of " + "players, action nodes, and function nodes"); + } stripComment(in); vector numTypes(N); // input number of types for each player for (int i = 0; i < N; ++i) { - if (in.eof() || in.bad()) { + in >> numTypes[i]; + if (!in) { throw std::runtime_error( "Error in game file: integer expected for the number of types for player " + std::to_string(i)); } - in >> numTypes[i]; } // input the type distributions @@ -93,10 +97,10 @@ std::shared_ptr BAGG::makeBAGG(istream &in) for (int i = 0; i < N; ++i) { TDist.emplace_back(numTypes[i]); for (int j = 0; j < numTypes[i]; ++j) { - if (in.eof() || in.bad()) { + in >> TDist[i][j]; + if (!in) { throw std::runtime_error("Error in game file: number expected for type distribution"); } - in >> TDist[i][j]; } } @@ -105,12 +109,12 @@ std::shared_ptr BAGG::makeBAGG(istream &in) vector>> typeActionSets(N); for (int i = 0; i < N; ++i) { for (int j = 0; j < numTypes[i]; ++j) { - if (in.eof() || in.bad()) { + int temp; + in >> temp; + if (!in || temp < 0) { throw std::runtime_error( "Error in game file: integer expected for size of type action set"); } - int temp; - in >> temp; typeActionSets[i].emplace_back(temp); } } @@ -120,10 +124,10 @@ std::shared_ptr BAGG::makeBAGG(istream &in) for (int i = 0; i < N; ++i) { for (int j = 0; j < numTypes[i]; ++j) { for (int &el : typeActionSets[i][j]) { - if (in.eof() || in.bad()) { + in >> el; + if (!in) { throw std::runtime_error("Error in game file: integer expected for type action set"); } - in >> el; } } } diff --git a/src/games/file.cc b/src/games/file.cc index ea033eebf4..db2adc1b5e 100644 --- a/src/games/file.cc +++ b/src/games/file.cc @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "gambit.h" @@ -318,6 +319,53 @@ class TableFileGame { } }; +/// Normalizes labels in place so the resulting set is distinct and nonempty: +/// empty labels are given a suffix and repeated labels are de-duplicated by +/// appending "_n", choosing the next n not already present in the scope. +/// `p_get(element)` reads an element's label and `p_set(element, label)` +/// writes it, so this works both on a container of game objects (via GetLabel/SetLabel) +/// and on a container of raw label strings (read/write the string directly). +template +void NormalizeLabels(Container &&p_container, Getter p_get, Setter p_set) +{ + // NOLINTBEGIN(misc-const-correctness) + std::map counts; + std::set used; + // NOLINTEND(misc-const-correctness) + for (auto &&element : p_container) { + counts[p_get(element)] += 1; + used.insert(p_get(element)); + } + // NOLINTBEGIN(misc-const-correctness) + std::map visited; + // NOLINTEND(misc-const-correctness) + for (auto &&element : p_container) { + const auto label = p_get(element); + // A special case: If only one label is the empty string we still want to + // convert it to "_1" + if (counts[label] == 1 && label != "") { + continue; + } + // Generate the next "label_n" that is not already used in this scope, so + // that e.g. {"x", "x", "x_1"} does not renumber to a duplicate "x_1". + std::string candidate; + do { + const auto index = ++visited[label]; + candidate = label + "_" + std::to_string(index); + } while (used.count(candidate) > 0); + used.insert(candidate); + p_set(element, candidate); + } +} + +/// Normalizes a list of raw label strings. +template void NormalizeLabelStrings(Container &p_labels) +{ + NormalizeLabels( + p_labels, [](const std::string &s) { return s; }, + [](std::string &s, const std::string &v) { s = v; }); +} + void ReadPlayers(GameFileLexer &p_state, TableFileGame &p_data) { p_state.ExpectNextToken(TOKEN_LBRACE, "'{'"); @@ -389,23 +437,39 @@ void ReadOutcomeList(GameFileLexer &p_parser, Game &p_nfg) auto players = p_nfg->GetPlayers(); p_parser.GetNextToken(); + // Buffer raw labels + payoffs so labels can be normalized (unique, nonempty) + // before the outcome objects are created (NewOutcome now rejects empty/duplicate). + std::vector labels; + std::vector> payoff_lists; + while (p_parser.GetCurrentToken() == TOKEN_LBRACE) { p_parser.ExpectNextToken(TOKEN_TEXT, "outcome name"); - auto outcome = p_nfg->NewOutcome(); - outcome->SetLabel(p_parser.GetLastText()); + labels.push_back(p_parser.GetLastText()); p_parser.GetNextToken(); - for (auto player : players) { + std::vector payoffs; + for (size_t i = 0; i < players.size(); ++i) { p_parser.ExpectCurrentToken(TOKEN_NUMBER, "numerical payoff"); - outcome->SetPayoff(player, Number(p_parser.GetLastText())); + payoffs.emplace_back(p_parser.GetLastText()); p_parser.AcceptNextToken(TOKEN_COMMA); } + payoff_lists.push_back(payoffs); + p_parser.ExpectCurrentToken(TOKEN_RBRACE, "'}'"); p_parser.GetNextToken(); } - p_parser.ExpectCurrentToken(TOKEN_RBRACE, "'}'"); p_parser.GetNextToken(); + + NormalizeLabelStrings(labels); + for (size_t i = 0; i < labels.size(); ++i) { + auto outcome = p_nfg->NewOutcome(labels[i]); + auto player_it = players.begin(); + for (const auto &payoff : payoff_lists[i]) { + outcome->SetPayoff(*player_it, payoff); + ++player_it; + } + } } void ParseOutcomeBody(GameFileLexer &p_parser, Game &p_nfg) @@ -463,48 +527,67 @@ Game BuildNfg(GameFileLexer &p_parser, TableFileGame &p_data) // Temporary representation classes //========================================================================= +/// An outcome definition encountered during the parse. Outcomes are not +/// created until the whole tree has been read, so that their labels can be +/// normalized in one pass before creation, as NewOutcome enforces +/// the label requirements at creation time. +struct OutcomeRecord { + std::string m_label; + std::vector m_payoffs; +}; + class TreeData { public: - std::map m_outcomeMap; + std::map m_outcomeRecords; + /// Outcome ids in order of first occurrence in the file; determines the + /// creation order (and hence numbering) of the outcomes, matching the + /// order in which the previous implementation created them. + std::vector m_outcomeOrder; + /// Deferred node-to-outcome attachments, replayed after outcomes are created. + std::vector> m_nodeOutcomes; std::map> m_infosetMap; }; void ReadPlayers(GameFileLexer &p_state, Game &p_game, TreeData &p_treeData) { p_state.ExpectNextToken(TOKEN_LBRACE, "'{'"); + // Buffer the raw player labels so they can be normalized (made unique and nonempty) + // before the player objects are created. + // NOLINTBEGIN(misc-const-correctness) + std::vector player_labels; + // NOLINTEND(misc-const-correctness) while (p_state.GetNextToken() == TOKEN_TEXT) { - p_game->NewPlayer()->SetLabel(p_state.GetLastText()); + player_labels.push_back(p_state.GetLastText()); } p_state.ExpectCurrentToken(TOKEN_RBRACE, "'}'"); + NormalizeLabelStrings(player_labels); + for (const auto &label : player_labels) { + p_game->NewPlayer(label); + } } void CheckOutcomeDefinition(const GameFileLexer &p_state, int p_outcomeId, - const GameOutcome &p_outcome, const std::string &p_label, - const GameRep::Players &p_players, + const OutcomeRecord &p_record, const std::string &p_label, const std::vector &p_payoffs) { - if (p_outcome->GetLabel() != p_label) { + if (p_record.m_label != p_label) { p_state.OnParseError("Outcome label does not match previous definition " "(outcome " + std::to_string(p_outcomeId) + ")"); } - - if (p_players.size() != p_payoffs.size()) { + if (p_record.m_payoffs.size() != p_payoffs.size()) { p_state.OnParseError("Outcome payoff count mismatch " "(outcome " + std::to_string(p_outcomeId) + ")"); } - - auto player_it = p_players.begin(); - for (const auto &payoff : p_payoffs) { - if (p_outcome->GetPayoff(*player_it) != - static_cast(payoff)) { + for (size_t i = 0; i < p_payoffs.size(); ++i) { + if (static_cast(p_record.m_payoffs[i]) != + static_cast(p_payoffs[i])) { p_state.OnParseError("Outcome payoffs do not match previous definition " "(outcome " + - std::to_string(p_outcomeId) + ", player " + - std::to_string((*player_it)->GetNumber()) + ")"); + std::to_string(p_outcomeId) + ", player " + std::to_string(i + 1) + + ")"); } - ++player_it; } } @@ -532,32 +615,53 @@ void ParseOutcome(GameFileLexer &p_state, Game &p_game, TreeData &p_treeData, Ga p_state.ExpectCurrentToken(TOKEN_RBRACE, "'}'"); p_state.GetNextToken(); - GameOutcome outcome; - if (!contains(p_treeData.m_outcomeMap, outcomeId)) { - outcome = p_game->NewOutcome(); - p_treeData.m_outcomeMap[outcomeId] = outcome; - outcome->SetLabel(label); - auto player_it = p_game->GetPlayers().begin(); - for (const auto &payoff : payoffs) { - outcome->SetPayoff(*player_it, payoff); - ++player_it; - } + if (!contains(p_treeData.m_outcomeRecords, outcomeId)) { + p_treeData.m_outcomeRecords.emplace(outcomeId, OutcomeRecord{label, payoffs}); + p_treeData.m_outcomeOrder.push_back(outcomeId); } else { - outcome = p_treeData.m_outcomeMap.at(outcomeId); - CheckOutcomeDefinition(p_state, outcomeId, outcome, label, p_game->GetPlayers(), payoffs); + CheckOutcomeDefinition(p_state, outcomeId, p_treeData.m_outcomeRecords.at(outcomeId), label, + payoffs); } - p_game->SetOutcome(p_node, outcome); + p_treeData.m_nodeOutcomes.emplace_back(p_node, outcomeId); } else if (outcomeId != 0) { // The node entry does not contain information about the outcome. // This means the outcome should have been defined already. - try { - p_game->SetOutcome(p_node, p_treeData.m_outcomeMap.at(outcomeId)); - } - catch (std::out_of_range) { + if (!contains(p_treeData.m_outcomeRecords, outcomeId)) { p_state.OnParseError("Outcome not defined"); } + p_treeData.m_nodeOutcomes.emplace_back(p_node, outcomeId); + } +} + +/// Create the game's outcomes from the definitions buffered during the parse. +/// Labels are normalized in first-occurrence order before creation, so that +/// the label requirements enforced by NewOutcome (nonempty, unique) are +/// satisfied; this matches the treatment of outcome labels read from .nfg +/// files, and produces the same labels the previous post-parse normalization +/// pass produced. +void CreateOutcomes(const Game &p_game, const TreeData &p_treeData) +{ + std::vector labels; + for (const int id : p_treeData.m_outcomeOrder) { + labels.push_back(p_treeData.m_outcomeRecords.at(id).m_label); + } + NormalizeLabelStrings(labels); + + std::map created; + auto label_it = labels.begin(); + for (const int id : p_treeData.m_outcomeOrder) { + auto outcome = p_game->NewOutcome(*label_it++); + auto player_it = p_game->GetPlayers().begin(); + for (const auto &payoff : p_treeData.m_outcomeRecords.at(id).m_payoffs) { + outcome->SetPayoff(*player_it, payoff); + ++player_it; + } + created.emplace(id, outcome); + } + for (const auto &[node, id] : p_treeData.m_nodeOutcomes) { + p_game->SetOutcome(node, created.at(id)); } } @@ -750,98 +854,31 @@ void ParseNode(GameFileLexer &p_state, Game p_game, GameNode p_node, TreeData &p } // end of anonymous namespace -#include "core/tinyxml.h" +#include "workspace.h" namespace Gambit { -class GameXMLSavefile { -private: - TiXmlDocument doc; - -public: - explicit GameXMLSavefile(const std::string &p_xml); - ~GameXMLSavefile() = default; - - Game GetGame() const; -}; - -GameXMLSavefile::GameXMLSavefile(const std::string &p_xml) -{ - doc.Parse(p_xml.c_str()); - if (doc.Error()) { - throw InvalidFileException("Not a valid XML document"); - } -} - -Game GameXMLSavefile::GetGame() const -{ - const TiXmlNode *docroot = doc.FirstChild("gambit:document"); - if (!docroot) { - throw InvalidFileException("Not a Gambit game savefile document"); - } - - const TiXmlNode *game = docroot->FirstChild("game"); - if (!game) { - throw InvalidFileException("No game representation found in document"); - } - - const TiXmlNode *efgfile = game->FirstChild("efgfile"); - if (efgfile) { - std::istringstream s(efgfile->FirstChild()->Value()); - return ReadGame(s); - } - - const TiXmlNode *nfgfile = game->FirstChild("nfgfile"); - if (nfgfile) { - std::istringstream s(nfgfile->FirstChild()->Value()); - return ReadGame(s); - } - - throw InvalidFileException("No game representation found in document"); -} - -template void NormalizeLabels(C &&p_container) -{ - // NOLINTBEGIN(misc-const-correctness) - std::map counts; - // NOLINTEND(misc-const-correctness) - for (const auto &element : p_container) { - counts[element->GetLabel()] += 1; - } - // NOLINTBEGIN(misc-const-correctness) - std::map visited; - // NOLINTEND(misc-const-correctness) - for (auto element : p_container) { - const auto label = element->GetLabel(); - // A special case: If only one label is the empty string we still want to - // convert it to "_1" - if (counts[label] == 1 && label != "") { - continue; - } - const auto index = ++visited[label]; - element->SetLabel(label + "_" + std::to_string(index)); - } -} - void NormalizeGameLabels(const Game &p_game) { - NormalizeLabels(p_game->GetPlayers()); - NormalizeLabels(p_game->GetOutcomes()); + const auto get_label = [](const auto &e) { return e->GetLabel(); }; + const auto set_label = [](const auto &e, const std::string &s) { e->SetLabel(s); }; + NormalizeLabels(p_game->GetPlayers(), get_label, set_label); + NormalizeLabels(p_game->GetOutcomes(), get_label, set_label); if (p_game->IsTree()) { for (const auto &player : p_game->GetPlayersWithChance()) { for (const auto &infoset : player->GetInfosets()) { - NormalizeLabels(infoset->GetActions()); + NormalizeLabels(infoset->GetActions(), get_label, set_label); } } } else { for (const auto &player : p_game->GetPlayers()) { - NormalizeLabels(player->GetStrategies()); + NormalizeLabels(player->GetStrategies(), get_label, set_label); } } } -Game ReadEfgFile(std::istream &p_stream, bool p_normalizeLabels /* = false */) +Game ReadEfgFile(std::istream &p_stream) { GameFileLexer parser(p_stream); @@ -869,42 +906,62 @@ Game ReadEfgFile(std::istream &p_stream, bool p_normalizeLabels /* = false */) parser.GetNextToken(); } ParseNode(parser, game, game->GetRoot(), treeData); - if (p_normalizeLabels) { - NormalizeGameLabels(game); - } + CreateOutcomes(game, treeData); + NormalizeGameLabels(game); return game; } -Game ReadNfgFile(std::istream &p_stream, bool p_normalizeLabels /* = false */) +Game ReadNfgFile(std::istream &p_stream) { GameFileLexer parser(p_stream); TableFileGame data; ParseNfgHeader(parser, data); - auto game = BuildNfg(parser, data); - if (p_normalizeLabels) { - NormalizeGameLabels(game); + // Normalize player and strategy labels on the raw lists before the game is + // built, so labels are unique and nonempty at construction. + for (auto &player : data.m_players) { + NormalizeLabelStrings(player.m_strategies); } + { + // NOLINTBEGIN(misc-const-correctness) + std::vector player_labels; + // NOLINTEND(misc-const-correctness) + for (const auto &player : data.m_players) { + player_labels.push_back(player.m_name); + } + NormalizeLabelStrings(player_labels); + auto label_it = player_labels.begin(); + for (auto &player : data.m_players) { + player.m_name = *label_it; + ++label_it; + } + } + auto game = BuildNfg(parser, data); + NormalizeGameLabels(game); return game; } -Game ReadGbtFile(std::istream &p_stream, bool p_normalizeLabels /* = false */) +Game ReadGbtFile(std::istream &p_stream) { - std::stringstream buffer; - buffer << p_stream.rdbuf(); - auto game = GameXMLSavefile(buffer.str()).GetGame(); - if (p_normalizeLabels) { + try { + const LegacyWorkspaceFile workspace = ReadLegacyWorkspace(p_stream); + std::istringstream game_text(workspace.game); + auto game = ReadGame(game_text); NormalizeGameLabels(game); + return game; + } + catch (const InvalidFileException &) { + throw; + } + catch (const std::runtime_error &) { + throw InvalidFileException("Not a valid .gbt document"); } - return game; } -Game ReadAggFile(std::istream &p_stream, bool p_normalizeLabels /* = false */) +Game ReadAggFile(std::istream &p_stream) { try { auto game = std::make_shared(agg::AGG::makeAGG(p_stream)); - if (p_normalizeLabels) { - NormalizeGameLabels(game); - } + NormalizeGameLabels(game); return game; } catch (std::runtime_error &ex) { @@ -912,13 +969,11 @@ Game ReadAggFile(std::istream &p_stream, bool p_normalizeLabels /* = false */) } } -Game ReadBaggFile(std::istream &p_stream, bool p_normalizeLabels /* = false */) +Game ReadBaggFile(std::istream &p_stream) { try { auto game = std::make_shared(agg::BAGG::makeBAGG(p_stream)); - if (p_normalizeLabels) { - NormalizeGameLabels(game); - } + NormalizeGameLabels(game); return game; } catch (std::runtime_error &ex) { @@ -926,7 +981,7 @@ Game ReadBaggFile(std::istream &p_stream, bool p_normalizeLabels /* = false */) } } -Game ReadGame(std::istream &p_file, bool p_normalizeLabels /* = false */) +Game ReadGame(std::istream &p_file) { std::stringstream buffer; buffer << p_file.rdbuf(); @@ -947,10 +1002,10 @@ Game ReadGame(std::istream &p_file, bool p_normalizeLabels /* = false */) } buffer.seekg(0, std::ios::beg); if (parser.GetLastText() == "NFG") { - return ReadNfgFile(buffer, p_normalizeLabels); + return ReadNfgFile(buffer); } if (parser.GetLastText() == "EFG") { - return ReadEfgFile(buffer, p_normalizeLabels); + return ReadEfgFile(buffer); } if (parser.GetLastText() == "#AGG") { return ReadAggFile(buffer); diff --git a/src/games/game.cc b/src/games/game.cc index a441bb0fb5..79c5cd7ddf 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -39,8 +39,10 @@ namespace Gambit { // class GameOutcomeRep //======================================================================== -GameOutcomeRep::GameOutcomeRep(GameRep *p_game, int p_number) : m_game(p_game), m_number(p_number) +GameOutcomeRep::GameOutcomeRep(GameRep *p_game, int p_number, const std::string &p_label) + : m_game(p_game), m_number(p_number), m_label(p_label) { + CheckLabel(p_label); for (const auto &player : m_game->m_players) { m_payoffs[player.get()] = Number(); } @@ -67,8 +69,8 @@ GameAction GameStrategyRep::GetAction(const GameInfoset &p_infoset) const // class GamePlayerRep //======================================================================== -GamePlayerRep::GamePlayerRep(GameRep *p_game, int p_id, int p_strats) - : m_game(p_game), m_number(p_id) +GamePlayerRep::GamePlayerRep(GameRep *p_game, int p_id, const std::string &p_label, int p_strats) + : m_game(p_game), m_number(p_id), m_label(p_label) { for (int j = 1; j <= p_strats; j++) { m_strategies.push_back(std::make_shared(this, j, "")); @@ -94,7 +96,7 @@ void GamePlayerRep::MakeStrategy(const std::map &behav) strategy->m_behav = behav; for (const auto &infoset : m_infosets) { strategy->m_label += (contains(strategy->m_behav, infoset.get())) - ? std::to_string(strategy->m_behav[infoset.get()]) + ? std::to_string(strategy->m_behav.at(infoset.get())) : "*"; } if (strategy->m_label.empty()) { @@ -303,8 +305,9 @@ MixedStrategyProfile::MixedStrategyProfile(const MixedBehaviorProfile &p_p for (const auto &strategy : player->m_strategies) { auto prob = static_cast(1); for (const auto &infoset : player->m_infosets) { - if (strategy->m_behav[infoset.get()] > 0) { - prob *= p_profile[infoset->GetAction(strategy->m_behav[infoset.get()])]; + if (contains(strategy->m_behav, infoset.get()) && + strategy->m_behav.at(infoset.get()) > 0) { + prob *= p_profile[infoset->GetAction(strategy->m_behav.at(infoset.get()))]; } } (*m_rep)[strategy] = prob; diff --git a/src/games/game.h b/src/games/game.h index a6a82f03ad..bd598b7237 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -184,7 +184,7 @@ class GameOutcomeRep : public std::enable_shared_from_this { /// @name Lifecycle //@{ /// Creates a new outcome object, with payoffs set to zero - GameOutcomeRep(GameRep *p_game, int p_number); + GameOutcomeRep(GameRep *p_game, int p_number, const std::string &p_label); ~GameOutcomeRep() = default; //@} @@ -201,11 +201,7 @@ class GameOutcomeRep : public std::enable_shared_from_this { /// Returns the text label associated with the outcome const std::string &GetLabel() const { return m_label; } /// Sets the text label associated with the outcome - void SetLabel(const std::string &p_label) - { - CheckLabel(p_label); - m_label = p_label; - } + void SetLabel(const std::string &p_label); /// Gets the payoff associated with the outcome to the player template const T &GetPayoff(const GamePlayer &p_player) const; @@ -289,11 +285,7 @@ class GameInfosetRep : public std::enable_shared_from_this { bool IsChanceInfoset() const; - void SetLabel(const std::string &p_label) - { - CheckLabel(p_label); - m_label = p_label; - } + void SetLabel(const std::string &p_label); const std::string &GetLabel() const { return m_label; } /// @name Actions @@ -373,11 +365,7 @@ class GameStrategyRep : public std::enable_shared_from_this { /// Returns the text label associated with the strategy const std::string &GetLabel() const { return m_label; } /// Sets the text label associated with the strategy - void SetLabel(const std::string &p_label) - { - CheckLabel(p_label); - m_label = p_label; - } + void SetLabel(const std::string &p_label); /// Returns the game on which the strategy is defined Game GetGame() const; @@ -464,8 +452,11 @@ class GamePlayerRep : public std::enable_shared_from_this { using Strategies = ElementCollection; using Sequences = ElementCollection; - GamePlayerRep(GameRep *p_game, int p_id) : m_game(p_game), m_number(p_id) {} - GamePlayerRep(GameRep *p_game, int p_id, int m_strats); + GamePlayerRep(GameRep *p_game, int p_id, const std::string &p_label) + : m_game(p_game), m_number(p_id), m_label(p_label) + { + } + GamePlayerRep(GameRep *p_game, int p_id, const std::string &p_label, int p_strats); ~GamePlayerRep(); bool IsValid() const { return m_valid; } @@ -475,11 +466,7 @@ class GamePlayerRep : public std::enable_shared_from_this { Game GetGame() const; const std::string &GetLabel() const { return m_label; } - void SetLabel(const std::string &p_label) - { - CheckLabel(p_label); - m_label = p_label; - } + void SetLabel(const std::string &p_label); bool IsChance() const { return (m_number == 0); } @@ -496,6 +483,8 @@ class GamePlayerRep : public std::enable_shared_from_this { GameStrategy GetStrategy(int st) const; /// Returns the collection of strategies available to the player Strategies GetStrategies() const; + /// Validate that p_label is a nonempty, valid, unique label for a strategy of this player. + void CheckStrategyLabel(const std::string &p_label) const; //@} /// @name Sequences @@ -554,11 +543,7 @@ class GameNodeRep : public std::enable_shared_from_this { Game GetGame() const; const std::string &GetLabel() const { return m_label; } - void SetLabel(const std::string &p_label) - { - CheckLabel(p_label); - m_label = p_label; - } + void SetLabel(const std::string &p_label); int GetNumber() const; GameNode GetChild(const GameAction &p_action) @@ -772,6 +757,10 @@ class GameRep : public std::enable_shared_from_this { /// Mark that the content of the game has changed void IncrementVersion() { m_version++; } void IndexStrategies() const; + /// Validate that p_label is a nonempty, valid, unique label for a player of this game, + void CheckPlayerLabel(const std::string &p_label) const; + /// Validate that p_label is a nonempty, valid, unique label for an outcome of this game. + void CheckOutcomeLabel(const std::string &p_label) const; //@} /// Hooks for derived classes to update lazily-computed orderings if required @@ -1170,7 +1159,7 @@ class GameRep : public std::enable_shared_from_this { virtual GamePlayer GetChance() const = 0; auto GetPlayersWithChance() const { return prepend_value(GetChance(), GetPlayers()); } /// Creates a new player in the game, with no moves - virtual GamePlayer NewPlayer() = 0; + virtual GamePlayer NewPlayer(const std::string &p_label) = 0; //@} /// @name Dimensions of the game @@ -1233,7 +1222,7 @@ class GameRep : public std::enable_shared_from_this { return Outcomes(std::const_pointer_cast(shared_from_this()), &m_outcomes); } /// Creates a new outcome in the game - virtual GameOutcome NewOutcome() { throw UndefinedException(); } + virtual GameOutcome NewOutcome(const std::string &p_label) { throw UndefinedException(); } /// Deletes the specified outcome from the game virtual void DeleteOutcome(const GameOutcome &) { throw UndefinedException(); } //@} @@ -1287,6 +1276,14 @@ class GameRep : public std::enable_shared_from_this { // all classes to be defined. inline Game GameOutcomeRep::GetGame() const { return m_game->shared_from_this(); } +inline void GameOutcomeRep::SetLabel(const std::string &p_label) +{ + if (p_label == m_label) { + return; + } + GetGame()->CheckOutcomeLabel(p_label); + m_label = p_label; +} template const T &GameOutcomeRep::GetPayoff(const GamePlayer &p_player) const { @@ -1319,6 +1316,27 @@ inline void GameOutcomeRep::SetPayoff(const GamePlayer &p_player, const Number & inline GamePlayer GameStrategyRep::GetPlayer() const { return m_player->shared_from_this(); } inline Game GameStrategyRep::GetGame() const { return m_player->GetGame(); } +inline void GameStrategyRep::SetLabel(const std::string &p_label) +{ + if (p_label == m_label) { + return; + } + GetPlayer()->CheckStrategyLabel(p_label); + m_label = p_label; +} + +inline void GamePlayerRep::CheckStrategyLabel(const std::string &p_label) const +{ + if (p_label.empty()) { + throw ValueException("Strategy label must not be empty"); + } + CheckLabel(p_label); + for (const auto &strategy : m_strategies) { + if (strategy->GetLabel() == p_label) { + throw ValueException("Strategy label must be unique for the player"); + } + } +} inline Game GameSequenceRep::GetGame() const { return m_player->GetGame(); } inline GamePlayer GameSequenceRep::GetPlayer() const { return m_player->shared_from_this(); } @@ -1327,9 +1345,64 @@ inline Game GameActionRep::GetGame() const { return m_infoset->GetGame(); } inline Game GameInfosetRep::GetGame() const { return m_game->shared_from_this(); } inline GamePlayer GameInfosetRep::GetPlayer() const { return m_player->shared_from_this(); } +inline void GameInfosetRep::SetLabel(const std::string &p_label) +{ + if (p_label == m_label) { + return; + } + CheckLabel(p_label); + // Infoset labels may be empty, but a non-empty label must be unique among + // the infosets of the same player. + if (!p_label.empty()) { + for (const auto &infoset : GetPlayer()->GetInfosets()) { + if (infoset.get() != this && infoset->GetLabel() == p_label) { + throw ValueException("Infoset label must be unique for the player"); + } + } + } + m_label = p_label; +} +inline void GameRep::CheckPlayerLabel(const std::string &p_label) const +{ + if (p_label.empty()) { + throw ValueException("Player label must not be empty"); + } + CheckLabel(p_label); + if (IsTree() && p_label == GetChance()->GetLabel()) { + throw ValueException("Player label must not be the reserved chance player label"); + } + for (const auto &player : m_players) { + if (player->GetLabel() == p_label) { + throw ValueException("Player label must be unique within the game"); + } + } +} +inline void GameRep::CheckOutcomeLabel(const std::string &p_label) const +{ + if (p_label.empty()) { + throw ValueException("Outcome label must not be empty"); + } + CheckLabel(p_label); + for (const auto &outcome : m_outcomes) { + if (outcome->GetLabel() == p_label) { + throw ValueException("Outcome label must be unique within the game"); + } + } +} inline bool GameInfosetRep::IsChanceInfoset() const { return m_player->IsChance(); } inline Game GamePlayerRep::GetGame() const { return m_game->shared_from_this(); } +inline void GamePlayerRep::SetLabel(const std::string &p_label) +{ + if (IsChance()) { + throw ValueException("The chance player's label cannot be changed"); + } + if (p_label == m_label) { + return; + } + GetGame()->CheckPlayerLabel(p_label); + m_label = p_label; +} inline GameStrategy GamePlayerRep::GetStrategy(int st) const { m_game->BuildComputedValues(); @@ -1347,6 +1420,22 @@ inline GamePlayerRep::Sequences GamePlayerRep::GetSequences() const } inline Game GameNodeRep::GetGame() const { return m_game->shared_from_this(); } +inline void GameNodeRep::SetLabel(const std::string &p_label) +{ + if (p_label == m_label) { + return; + } + CheckLabel(p_label); + // Node labels may be empty, but a non-empty label must be unique within the game. + if (!p_label.empty()) { + for (const auto &node : GetGame()->GetNodes()) { + if (node.get() != this && node->GetLabel() == p_label) { + throw ValueException("Node label must be unique within the game"); + } + } + } + m_label = p_label; +} inline int GameNodeRep::GetNumber() const { m_game->EnsureNodeOrdering(); @@ -1395,38 +1484,32 @@ Game NewTable(const std::vector &p_dim, bool p_sparseOutcomes = false); /// @brief Reads a game representation in .efg format /// /// @param[in] p_stream An input stream, positioned at the start of the text in .efg format -/// @param[in] p_normalizeLabels Require element labels to be nonempty and unique within -/// their scope /// @return A handle to the game representation constructed /// @throw InvalidFileException If the stream does not contain a valid serialisation /// of a game in .efg format. /// @sa Game::WriteEfgFile, ReadNfgFile, ReadAggFile, ReadBaggFile -Game ReadEfgFile(std::istream &p_stream, bool p_normalizeLabels = false); +Game ReadEfgFile(std::istream &p_stream); /// @brief Reads a game representation in .nfg format /// @param[in] p_stream An input stream, positioned at the start of the text in .nfg format -/// @param[in] p_normalizeLabels Require element labels to be nonempty and unique within -/// their scope /// @return A handle to the game representation constructed /// @throw InvalidFileException If the stream does not contain a valid serialisation /// of a game in .nfg format. /// @sa Game::WriteNfgFile, ReadEfgFile, ReadAggFile, ReadBaggFile -Game ReadNfgFile(std::istream &p_stream, bool p_normalizeLabels = false); +Game ReadNfgFile(std::istream &p_stream); /// @brief Reads a game representation from a graphical interface XML saveflie /// @param[in] p_stream An input stream, positioned at the start of the text -/// @param[in] p_normalizeLabels Require element labels to be nonempty and unique within -/// their scope /// @return A handle to the game representation constructed /// @throw InvalidFileException If the stream does not contain a valid serialisation /// of a game in an XML savefile /// @sa ReadEfgFile, ReadNfgFile, ReadAggFile, ReadBaggFile -Game ReadGbtFile(std::istream &p_stream, bool p_normalizeLabels = false); +Game ReadGbtFile(std::istream &p_stream); /// @brief Reads a game from the input stream, attempting to autodetect file format /// @deprecated Deprecated in favour of the various ReadXXXGame functions. /// @sa ReadEfgFile, ReadNfgFile, ReadGbtFile, ReadAggFile, ReadBaggFile -Game ReadGame(std::istream &p_stream, bool p_normalizeLabels = false); +Game ReadGame(std::istream &p_stream); /// @brief Generate a distribution over a simplex restricted to rational numbers of given /// denominator diff --git a/src/games/gameagg.cc b/src/games/gameagg.cc index a702e8b011..af223c993b 100644 --- a/src/games/gameagg.cc +++ b/src/games/gameagg.cc @@ -179,8 +179,8 @@ template class AGGMixedStrategyProfileRep; GameAGGRep::GameAGGRep(std::shared_ptr p_aggPtr) : aggPtr(p_aggPtr) { for (int pl = 1; pl <= aggPtr->getNumPlayers(); pl++) { - m_players.push_back(std::make_shared(this, pl, aggPtr->getNumActions(pl - 1))); - m_players.back()->m_label = lexical_cast(pl); + m_players.push_back(std::make_shared(this, pl, lexical_cast(pl), + aggPtr->getNumActions(pl - 1))); std::for_each(m_players.back()->m_strategies.begin(), m_players.back()->m_strategies.end(), [st = 1](const std::shared_ptr &s) mutable { s->m_label = std::to_string(st++); diff --git a/src/games/gameagg.h b/src/games/gameagg.h index bf000019eb..609924e6ad 100644 --- a/src/games/gameagg.h +++ b/src/games/gameagg.h @@ -63,7 +63,7 @@ class GameAGGRep : public GameRep { /// Returns the chance (nature) player GamePlayer GetChance() const override { throw UndefinedException(); } /// Creates a new player in the game, with no moves - GamePlayer NewPlayer() override { throw UndefinedException(); } + GamePlayer NewPlayer(const std::string &) override { throw UndefinedException(); } //@} /// @name Nodes @@ -113,15 +113,7 @@ class GameAGGRep : public GameRep { /// @return A handle to the game representation constructed /// @throw InvalidFileException If the stream does not contain a valid serialisation /// of a game in .agg format. -inline Game ReadAggFile(std::istream &p_stream) -{ - try { - return std::make_shared(agg::AGG::makeAGG(p_stream)); - } - catch (std::runtime_error &ex) { - throw InvalidFileException(ex.what()); - } -} +Game ReadAggFile(std::istream &p_stream); } // namespace Gambit diff --git a/src/games/gamebagg.cc b/src/games/gamebagg.cc index fea3bb695d..cfca1517fd 100644 --- a/src/games/gamebagg.cc +++ b/src/games/gamebagg.cc @@ -213,9 +213,8 @@ GameBAGGRep::GameBAGGRep(std::shared_ptr _baggPtr) int k = 1; for (int pl = 1; pl <= baggPtr->getNumPlayers(); pl++) { for (int j = 0; j < baggPtr->getNumTypes(pl - 1); j++, k++) { - m_players.push_back( - std::make_shared(this, k, baggPtr->getNumActions(pl - 1, j))); - m_players.back()->m_label = std::to_string(k); + m_players.push_back(std::make_shared(this, k, std::to_string(k), + baggPtr->getNumActions(pl - 1, j))); agent2baggPlayer[k] = pl; std::for_each(m_players.back()->m_strategies.begin(), m_players.back()->m_strategies.end(), [st = 1](const std::shared_ptr &s) mutable { diff --git a/src/games/gamebagg.h b/src/games/gamebagg.h index b4a28cb438..c51a8a661f 100644 --- a/src/games/gamebagg.h +++ b/src/games/gamebagg.h @@ -70,7 +70,7 @@ class GameBAGGRep : public GameRep { /// Returns the chance (nature) player GamePlayer GetChance() const override { throw UndefinedException(); } /// Creates a new player in the game, with no moves - GamePlayer NewPlayer() override { throw UndefinedException(); } + GamePlayer NewPlayer(const std::string &) override { throw UndefinedException(); } //@} /// @name Nodes @@ -121,15 +121,7 @@ class GameBAGGRep : public GameRep { /// @return A handle to the game representation constructed /// @throw InvalidFileException If the stream does not contain a valid serialisation /// of a game in .bagg format. -inline Game ReadBaggFile(std::istream &in) -{ - try { - return std::make_shared(agg::BAGG::makeBAGG(in)); - } - catch (std::runtime_error &ex) { - throw InvalidFileException(ex.what()); - } -} +Game ReadBaggFile(std::istream &in); } // end namespace Gambit diff --git a/src/games/gameexpl.cc b/src/games/gameexpl.cc index 718b48dea9..90823eb3bc 100644 --- a/src/games/gameexpl.cc +++ b/src/games/gameexpl.cc @@ -58,9 +58,10 @@ Rational GameExplicitRep::GetMaxPayoff() const // GameExplicitRep: Outcomes //------------------------------------------------------------------------ -GameOutcome GameExplicitRep::NewOutcome() +GameOutcome GameExplicitRep::NewOutcome(const std::string &p_label) { - m_outcomes.push_back(std::make_shared(this, m_outcomes.size() + 1)); + CheckOutcomeLabel(p_label); + m_outcomes.push_back(std::make_shared(this, m_outcomes.size() + 1, p_label)); return m_outcomes.back(); } diff --git a/src/games/gameexpl.h b/src/games/gameexpl.h index 06a72308b3..092e3b9708 100644 --- a/src/games/gameexpl.h +++ b/src/games/gameexpl.h @@ -42,7 +42,7 @@ class GameExplicitRep : public GameRep { /// @name Outcomes //@{ /// Creates a new outcome in the game - GameOutcome NewOutcome() override; + GameOutcome NewOutcome(const std::string &p_label) override; /// @name Writing data files //@{ diff --git a/src/games/gametable.cc b/src/games/gametable.cc index 078969d1a9..4bee172d81 100644 --- a/src/games/gametable.cc +++ b/src/games/gametable.cc @@ -376,8 +376,9 @@ GameTableRep::GameTableRep(const std::vector &dim, bool p_sparseOutcomes /* : m_results(std::accumulate(dim.begin(), dim.end(), 1, std::multiplies<>())) { for (const auto &nstrat : dim) { - m_players.push_back(std::make_shared(this, m_players.size() + 1, nstrat)); - m_players.back()->m_label = lexical_cast(m_players.size()); + const auto pl = m_players.size() + 1; + m_players.push_back( + std::make_shared(this, pl, lexical_cast(pl), nstrat)); std::for_each(m_players.back()->m_strategies.begin(), m_players.back()->m_strategies.end(), [st = 1](const std::shared_ptr &s) mutable { s->m_label = std::to_string(st++); @@ -391,7 +392,7 @@ GameTableRep::GameTableRep(const std::vector &dim, bool p_sparseOutcomes /* else { m_outcomes = std::vector>(m_results.size()); std::generate(m_outcomes.begin(), m_outcomes.end(), [this, outc = 1]() mutable { - return std::make_shared(this, outc++); + return std::make_shared(this, outc++, ""); }); std::transform(m_outcomes.begin(), m_outcomes.end(), m_results.begin(), [](const std::shared_ptr &c) { return c.get(); }); @@ -494,10 +495,11 @@ void GameTableRep::WriteNfgFile(std::ostream &p_file) const // GameTableRep: Players //------------------------------------------------------------------------ -GamePlayer GameTableRep::NewPlayer() +GamePlayer GameTableRep::NewPlayer(const std::string &p_label) { + CheckPlayerLabel(p_label); + auto player = std::make_shared(this, m_players.size() + 1, p_label, 1); IncrementVersion(); - auto player = std::make_shared(this, m_players.size() + 1, 1); m_players.push_back(player); for (const auto &outcome : m_outcomes) { outcome->m_payoffs[player.get()] = Number(); @@ -531,13 +533,15 @@ GameStrategy GameTableRep::NewStrategy(const GamePlayer &p_player, const std::st if (p_player->GetGame().get() != this) { throw MismatchException(); } + p_player->CheckStrategyLabel(p_label); + auto strategy = std::make_shared(p_player.get(), + p_player->m_strategies.size() + 1, p_label); IncrementVersion(); std::vector old_radices; for (const auto &player : m_players) { old_radices.push_back(player->m_strategies.size()); } - p_player->m_strategies.push_back(std::make_shared( - p_player.get(), p_player->m_strategies.size() + 1, p_label)); + p_player->m_strategies.push_back(strategy); RebuildTable(old_radices); return p_player->m_strategies.back(); } diff --git a/src/games/gametable.h b/src/games/gametable.h index 04facee7d9..df331c363a 100644 --- a/src/games/gametable.h +++ b/src/games/gametable.h @@ -76,7 +76,7 @@ class GameTableRep : public GameExplicitRep { /// Returns the chance (nature) player GamePlayer GetChance() const override { throw UndefinedException(); } /// Creates a new player in the game, with no moves - GamePlayer NewPlayer() override; + GamePlayer NewPlayer(const std::string &p_label) override; //@} /// @name Nodes diff --git a/src/games/gametree.cc b/src/games/gametree.cc index e89c4f0cf9..84d6ec0fd1 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -718,7 +718,7 @@ GameInfoset GameTreeRep::InsertMove(GameNode p_node, GameInfoset p_infoset) GameTreeRep::GameTreeRep() : m_root(std::make_shared(this, nullptr)), - m_chance(std::make_shared(this, 0)) + m_chance(std::make_shared(this, 0, "Chance")) { } @@ -1524,10 +1524,11 @@ int GameTreeRep::BehavProfileLength() const // GameTreeRep: Players //------------------------------------------------------------------------ -GamePlayer GameTreeRep::NewPlayer() +GamePlayer GameTreeRep::NewPlayer(const std::string &p_label) { + CheckPlayerLabel(p_label); + auto player = std::make_shared(this, m_players.size() + 1, p_label); IncrementVersion(); - auto player = std::make_shared(this, m_players.size() + 1); m_players.push_back(player); for (const auto &outcome : m_outcomes) { outcome->m_payoffs[player.get()] = Number(); @@ -1740,7 +1741,7 @@ Rational TreePureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const for (const auto &player : m_game->GetPlayers()) { for (const auto &infoset : player->GetInfosets()) { try { - behav.SetAction(infoset->GetAction(GetStrategy(player)->m_behav[infoset.get()])); + behav.SetAction(infoset->GetAction(GetStrategy(player)->m_behav.at(infoset.get()))); } catch (std::out_of_range &) { } diff --git a/src/games/gametree.h b/src/games/gametree.h index 4cb6e09dd2..1b2e15e9e5 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -136,7 +136,7 @@ class GameTreeRep final : public GameExplicitRep { /// Returns the chance (nature) player GamePlayer GetChance() const override { return m_chance->shared_from_this(); } /// Creates a new player in the game, with no moves - GamePlayer NewPlayer() override; + GamePlayer NewPlayer(const std::string &p_label) override; //@} /// @name Nodes diff --git a/src/games/nash.h b/src/games/nash.h index c8efdfdc7b..322c4a2438 100644 --- a/src/games/nash.h +++ b/src/games/nash.h @@ -29,22 +29,16 @@ namespace Gambit::Nash { template -using StrategyCallbackType = - std::function &, const std::string &)>; +using StrategyCallbackType = std::function &)>; /// @brief A fallback callback function for mixed strategy profiles that does nothing -template void NullStrategyCallback(const MixedStrategyProfile &, const std::string &) -{ -} +template void NullStrategyCallback(const MixedStrategyProfile &) {} template -using BehaviorCallbackType = - std::function &, const std::string &)>; +using BehaviorCallbackType = std::function &)>; /// @brief A fallback callback function for mixed behavior profiles that does nothing -template void NullBehaviorCallback(const MixedBehaviorProfile &, const std::string &) -{ -} +template void NullBehaviorCallback(const MixedBehaviorProfile &) {} template std::list> @@ -57,20 +51,6 @@ ToMixedBehaviorProfile(const std::list> &p_list) return ret; } -template -using BehaviorSolverType = std::function>(const Game &)>; - -// -// Exception raised when maximum number of equilibria to compute -// has been reached. A convenience for unraveling a potentially -// deep recursion. -// -class EquilibriumLimitReached : public std::runtime_error { -public: - EquilibriumLimitReached() : std::runtime_error("Reached target number of equilibria") {} - ~EquilibriumLimitReached() noexcept override = default; -}; - } // namespace Gambit::Nash #endif // LIBGAMBIT_NASH_H diff --git a/src/games/workspace.cc b/src/games/workspace.cc new file mode 100644 index 0000000000..a2aaaad877 --- /dev/null +++ b/src/games/workspace.cc @@ -0,0 +1,556 @@ +// +// This file is part of Gambit +// Copyright (c) 1994-2026, The Gambit Project (https://www.gambit-project.org) +// +// FILE: src/games/workspace.cc +// Reader and writer for Gambit's legacy .gbt workspace format +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// + +#include "workspace.h" + +#include +#include +#include +#include +#include +#include + +namespace Gambit { +namespace { + +class Element { + friend class Parser; + + std::string m_name; + std::string m_text; + std::map m_attributes; + std::vector> m_children; + +public: + const std::string &Name() const { return m_name; } + const std::string &Text() const { return m_text; } + const std::string *Attribute(const std::string &p_name) const; + bool IntAttribute(const std::string &p_name, int &p_value) const; + const Element *Child(const std::string &p_name) const; + std::vector Children(const std::string &p_name = {}) const; +}; + +class Parser { + const std::string &m_input; + size_t m_pos{0}; + + [[noreturn]] void Fail(const std::string &p_message) const + { + throw std::runtime_error("Invalid .gbt XML at byte " + std::to_string(m_pos) + ": " + + p_message); + } + + bool StartsWith(const std::string &p_text) const + { + return m_input.compare(m_pos, p_text.size(), p_text) == 0; + } + + void SkipWhitespace() + { + while (m_pos < m_input.size() && (m_input[m_pos] == ' ' || m_input[m_pos] == '\t' || + m_input[m_pos] == '\r' || m_input[m_pos] == '\n')) { + ++m_pos; + } + } + + void SkipMarkup() + { + if (StartsWith("", m_pos + 2); + if (end == std::string::npos) { + Fail("unterminated processing instruction"); + } + m_pos = end + 2; + } + else if (StartsWith("", m_pos + 4); + if (end == std::string::npos) { + Fail("unterminated comment"); + } + m_pos = end + 3; + } + else { + Fail("unsupported markup"); + } + } + + std::string Name() + { + const size_t begin = m_pos; + while (m_pos < m_input.size()) { + const char c = m_input[m_pos]; + if (!(std::isalnum(static_cast(c)) || c == '_' || c == '-' || c == ':' || + c == '.')) { + break; + } + ++m_pos; + } + if (begin == m_pos) { + Fail("expected a name"); + } + return m_input.substr(begin, m_pos - begin); + } + + static void AppendUtf8(std::string &p_out, unsigned long p_codepoint) + { + if (p_codepoint <= 0x7f) { + p_out.push_back(static_cast(p_codepoint)); + } + else if (p_codepoint <= 0x7ff) { + p_out.push_back(static_cast(0xc0 | (p_codepoint >> 6))); + p_out.push_back(static_cast(0x80 | (p_codepoint & 0x3f))); + } + else if (p_codepoint <= 0xffff) { + p_out.push_back(static_cast(0xe0 | (p_codepoint >> 12))); + p_out.push_back(static_cast(0x80 | ((p_codepoint >> 6) & 0x3f))); + p_out.push_back(static_cast(0x80 | (p_codepoint & 0x3f))); + } + else if (p_codepoint <= 0x10ffff) { + p_out.push_back(static_cast(0xf0 | (p_codepoint >> 18))); + p_out.push_back(static_cast(0x80 | ((p_codepoint >> 12) & 0x3f))); + p_out.push_back(static_cast(0x80 | ((p_codepoint >> 6) & 0x3f))); + p_out.push_back(static_cast(0x80 | (p_codepoint & 0x3f))); + } + else { + throw std::runtime_error("Invalid Unicode character in .gbt XML"); + } + } + + std::string Decode(const std::string &p_text) const + { + std::string result; + for (size_t pos = 0; pos < p_text.size();) { + if (p_text[pos] != '&') { + result.push_back(p_text[pos++]); + continue; + } + const auto end = p_text.find(';', pos + 1); + if (end == std::string::npos) { + throw std::runtime_error("Invalid entity in .gbt XML"); + } + const std::string entity = p_text.substr(pos + 1, end - pos - 1); + if (entity == "amp") { + result.push_back('&'); + } + else if (entity == "lt") { + result.push_back('<'); + } + else if (entity == "gt") { + result.push_back('>'); + } + else if (entity == "quot") { + result.push_back('"'); + } + else if (entity == "apos") { + result.push_back('\''); + } + else if (!entity.empty() && entity[0] == '#') { + const bool hex = entity.size() > 1 && (entity[1] == 'x' || entity[1] == 'X'); + const std::string digits = entity.substr(hex ? 2 : 1); + unsigned long value = 0; + try { + value = std::stoul(digits, nullptr, hex ? 16 : 10); + } + catch (...) { + throw std::runtime_error("Invalid numeric entity in .gbt XML"); + } + AppendUtf8(result, value); + } + else { + throw std::runtime_error("Unknown entity in .gbt XML"); + } + pos = end + 1; + } + return result; + } + +public: + explicit Parser(const std::string &p_input) : m_input(p_input) {} + + std::unique_ptr ParseElement() + { + if (m_pos >= m_input.size() || m_input[m_pos++] != '<') { + Fail("expected '<'"); + } + auto element = std::make_unique(); + element->m_name = Name(); + while (true) { + SkipWhitespace(); + if (StartsWith("/>")) { + m_pos += 2; + return element; + } + if (StartsWith(">")) { + ++m_pos; + break; + } + const std::string name = Name(); + SkipWhitespace(); + if (m_pos >= m_input.size() || m_input[m_pos++] != '=') { + Fail("expected '='"); + } + SkipWhitespace(); + if (m_pos >= m_input.size() || (m_input[m_pos] != '"' && m_input[m_pos] != '\'')) { + Fail("expected quoted attribute value"); + } + const char quote = m_input[m_pos++]; + const size_t begin = m_pos; + const auto end = m_input.find(quote, begin); + if (end == std::string::npos) { + Fail("unterminated attribute value"); + } + element->m_attributes[name] = Decode(m_input.substr(begin, end - begin)); + m_pos = end + 1; + } + + // SaveWorkspace embeds these payloads verbatim rather than XML-escaping + // them. Treat them as opaque text so game labels and descriptions that + // contain '<' or '&' remain readable. + if (element->m_name == "efgfile" || element->m_name == "nfgfile" || + element->m_name == "description" || element->m_name == "profile") { + const std::string close = "m_name + ">"; + const auto end = m_input.find(close, m_pos); + if (end == std::string::npos) { + Fail("unterminated element " + element->m_name); + } + element->m_text = m_input.substr(m_pos, end - m_pos); + m_pos = end + close.size(); + return element; + } + + while (true) { + if (m_pos >= m_input.size()) { + Fail("unterminated element " + element->m_name); + } + if (StartsWith("= m_input.size() || m_input[m_pos++] != '>') { + Fail("expected '>'"); + } + if (closing != element->m_name) { + Fail("mismatched closing element " + closing); + } + return element; + } + if (StartsWith("