pax_global_header00006660000000000000000000000064147642525520014526gustar00rootroot0000000000000052 comment=addcce32a3bd6a09303f052bd59fb758621d30fb libxmlb-0.3.22/000077500000000000000000000000001476425255200132435ustar00rootroot00000000000000libxmlb-0.3.22/.clang-format000066400000000000000000000024261476425255200156220ustar00rootroot00000000000000--- AlignAfterOpenBracket: 'Align' AlignConsecutiveAssignments: 'false' AlignConsecutiveDeclarations: 'false' AlignConsecutiveMacros: 'true' AlignOperands: 'true' AlignTrailingComments: 'true' AllowAllArgumentsOnNextLine: 'false' AllowAllParametersOfDeclarationOnNextLine: 'false' AllowShortBlocksOnASingleLine: 'false' AllowShortCaseLabelsOnASingleLine: 'false' AllowShortFunctionsOnASingleLine: 'Inline' AllowShortIfStatementsOnASingleLine: 'false' AlwaysBreakAfterReturnType: 'All' BinPackParameters: 'false' BinPackArguments: 'false' BreakBeforeBraces: 'Linux' ColumnLimit: '100' DerivePointerAlignment: 'false' IndentCaseLabels: 'false' IndentWidth: '8' IncludeBlocks: 'Regroup' KeepEmptyLinesAtTheStartOfBlocks: 'false' MaxEmptyLinesToKeep: '1' PointerAlignment: 'Right' SortIncludes: 'true' SpaceAfterCStyleCast: 'false' SpaceBeforeAssignmentOperators : 'true' SpaceBeforeParens: 'ControlStatements' SpaceInEmptyParentheses: 'false' SpacesInSquareBrackets: 'false' TabWidth: '8' UseTab: 'Always' PenaltyBreakAssignment: '3' PenaltyBreakBeforeFirstCallParameter: '15' --- Language: 'Proto' --- Language: 'Cpp' IncludeCategories: - Regex: '^"config.h"$' Priority: '0' - Regex: '^<' Priority: '1' - Regex: '.*' Priority: '2' ... libxmlb-0.3.22/.clang-tidy000066400000000000000000000023561476425255200153050ustar00rootroot00000000000000--- Checks: "-*,\ bugprone-*,\ -bugprone-assignment-in-if-condition,\ -bugprone-easily-swappable-parameters,\ -bugprone-implicit-widening-of-multiplication-result,\ -bugprone-macro-parentheses,\ -bugprone-misplaced-widening-cast,\ -bugprone-narrowing-conversions,\ -bugprone-reserved-identifier,\ -bugprone-too-small-loop-variable,\ -bugprone-unchecked-optional-access,\ misc-*,\ -misc-confusable-identifiers,\ -misc-const-correctness,\ -misc-non-private-member-variables-in-classes,\ -misc-no-recursion,\ -misc-static-assert,\ -misc-unused-parameters,\ modernize-*,\ -modernize-macro-to-enum,\ -modernize-use-trailing-return-type,\ -modernize-use-transparent-functors,\ performance-*,\ -performance-inefficient-vector-operation,\ -performance-no-int-to-ptr,\ readability-*,\ -readability-avoid-const-params-in-decls,\ -readability-braces-around-statements,\ -readability-function-cognitive-complexity,\ -readability-identifier-length,\ -readability-identifier-naming,\ -readability-implicit-bool-conversion,\ -readability-isolate-declaration,\ -readability-magic-numbers,\ -readability-non-const-parameter,\ -readability-qualified-auto,\ -readability-redundant-declaration,\ -readability-suspicious-call-argument,\ -readability-uppercase-literal-suffix,\ " ... libxmlb-0.3.22/.editorconfig000066400000000000000000000001611476425255200157160ustar00rootroot00000000000000# See https://editorconfig.org/ root = true [*] indent_style = tab indent_size = 8 [*.py] indent_style = space libxmlb-0.3.22/.git-blame-ignore-revs000066400000000000000000000000511476425255200173370ustar00rootroot000000000000007f4351abe299db820dbb2112dbc0743de358850e libxmlb-0.3.22/.github/000077500000000000000000000000001476425255200146035ustar00rootroot00000000000000libxmlb-0.3.22/.github/dependabot.yml000066400000000000000000000001661476425255200174360ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" libxmlb-0.3.22/.github/workflows/000077500000000000000000000000001476425255200166405ustar00rootroot00000000000000libxmlb-0.3.22/.github/workflows/main.yml000066400000000000000000000010721476425255200203070ustar00rootroot00000000000000name: Build and Test on: push: branches: [ main ] pull_request: branches: [ main ] permissions: contents: read jobs: build-linux: runs-on: ubuntu-latest strategy: matrix: distro: - fedora - fedora-w64 - debian fail-fast: false steps: - uses: actions/checkout@v4 - run: docker build -t libxmlb-${{ matrix.distro }} -f contrib/ci/Dockerfile-${{ matrix.distro }} . - run: docker run -t -v `pwd`:/build libxmlb-${{ matrix.distro }} ./contrib/ci/build-${{ matrix.distro }}.sh libxmlb-0.3.22/.github/workflows/scorecard.yml000066400000000000000000000060651476425255200213370ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '25 11 * * 1' push: branches: [ "main" ] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write # Uncomment the permissions below if installing in a private repository. # contents: read # actions: read steps: - name: "Checkout code" uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecard on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 with: sarif_file: results.sarif libxmlb-0.3.22/.gitignore000066400000000000000000000000061476425255200152270ustar00rootroot00000000000000build libxmlb-0.3.22/.pre-commit-config.yaml000066400000000000000000000023411476425255200175240ustar00rootroot00000000000000default_stages: [commit] repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 hooks: - id: no-commit-to-branch args: [--branch, main, --pattern, 0_.*_X] - id: check-added-large-files - id: check-byte-order-marker - id: check-executables-have-shebangs - id: forbid-new-submodules - id: check-yaml exclude: '.clang-format' - id: check-xml - id: end-of-file-fixer types_or: [c, shell, python] - id: trailing-whitespace types_or: [c, shell, python, xml] - id: check-docstring-first - id: check-merge-conflict - id: mixed-line-ending args: [--fix=lf] - repo: https://github.com/ambv/black rev: 22.3.0 hooks: - id: black - repo: local hooks: - id: check-null-false-returns name: check for null / false return mistmatch language: script entry: ./contrib/ci/check-null-false-returns.py - id: clang-format name: clang-format language: script entry: ./contrib/reformat-code.py types: [c] - id: check-license name: Check license header types_or: [shell, c, python] language: script entry: ./contrib/ci/check-license.py libxmlb-0.3.22/CODE_OF_CONDUCT.md000066400000000000000000000062201476425255200160420ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at richard@hughsie.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ libxmlb-0.3.22/LICENSE000066400000000000000000000636361476425255200142660ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! libxmlb-0.3.22/MAINTAINERS000066400000000000000000000000451476425255200147370ustar00rootroot00000000000000Richard Hughes libxmlb-0.3.22/NEWS000066400000000000000000000351671476425255200137560ustar00rootroot00000000000000Version 0.3.22 ~~~~~~~~~~~~~~ Released: 2025-03-12 New Features: - Add support for COLLAPSE_EMPTY when exporting an XbBuilderNode (Richard Hughes) - Store the expected file size in the header to detect truncation (Richard Hughes) Bugfixes: - Check the strtab has a trailing NUL byte (Richard Hughes) - Fix an issue when exporting a silo using COLLAPSE_EMPTY (Richard Hughes) - Fix calling text() on an empty element (Richard Hughes) Version 0.3.21 ~~~~~~~~~~~~~~ Released: 2024-10-15 Bugfixes: - Check for corrupt XbSiloNode values in a smarter way (Richard Hughes) Version 0.3.20 ~~~~~~~~~~~~~~ Released: 2024-10-14 Bugfixes: - Do not always strip literal text (Richard Hughes) - Do not assume .txt files are application/xml (Richard Hughes) - Fix a crash when loading a corrupt XMLb store (Richard Hughes) - Fix writing files on Windows (Richard Hughes) Version 0.3.19 ~~~~~~~~~~~~~~ Released: 2024-04-22 New Features: - Add xb_version_string() to get the runtime ABI version (Richard Hughes) Bugfixes: - Add the runtime version as the default XMLb invalidation GUID (Richard Hughes) Version 0.3.18 ~~~~~~~~~~~~~~ Released: 2024-04-09 Bugfixes: - Fix decompressing large zstd files, harder (Richard Hughes) - Include the pkgconfig variables in the subproject declared dependency (Richard Hughes) Version 0.3.17 ~~~~~~~~~~~~~~ Released: 2024-04-05 Bugfixes: - Create SECURITY.md for the OpenSSF scorecard (Richard Hughes) - Fix decompressing large zstd files (Richard Hughes) - Use zst as the file extension for zstd (Richard Hughes) Version 0.3.16 ~~~~~~~~~~~~~~ Released: 2024-04-03 New Features: - Add the lzma and zstd support to the pkgconfig file (Richard Hughes) Bugfixes: - Make the LZMA support optional (Richard Hughes) Version 0.3.15 ~~~~~~~~~~~~~~ Released: 2024-01-02 New Features: - Sprinkle __attribute__((nonnull)) to give a little more compile-time safety (Richard Hughes) Bugfixes: - Accept text/xml as an alternative to application/xml (peigongdsd) - Do not inline shared code (Chun-wei Fan) - Fix compiling with Visual Studio (Chun-wei Fan) - Fix the exported api test on Windows (Chun-wei Fan) - Generate and use .def file for clang-cl builds (Chun-wei Fan) - Release source file handles early (Milan Crha) Version 0.3.14 ~~~~~~~~~~~~~~ Released: 2023-10-24 Bugfixes: - Fix comparing indexed text with integer values (Richard Hughes) Version 0.3.13 ~~~~~~~~~~~~~~ Released: 2023-10-17 Bugfixes: - Correctly tokenize when using a bound text value (Richard Hughes) - Ensure tokens are all NULL when using xb_opcode_init() (Richard Hughes) Version 0.3.12 ~~~~~~~~~~~~~~ Released: 2023-10-10 New Features: - Use indexes when binding value (Richard Hughes) Bugfixes: - Avoid building errors and debug strings when possible to make XbQuery faster (Christian Hergert) - Fix content type detection on macOS (Richard Hughes) - Inline a number of machine internals to make queries faster (Christian Hergert) - Make zstd support optional (sulincix, aliriza) - Setup various release build options (Christian Hergert) Version 0.3.11 ~~~~~~~~~~~~~~ Released: 2023-02-20 New Features: - Add limited support for XPath 1.0 'in' (Richard Hughes) - Add support for zstd (Richard Hughes) Bugfixes: - Do not assert() when decompressing invalid LZMA (Richard Hughes) Version 0.3.10 ~~~~~~~~~~~~~~ Released: 2022-09-11 Bugfixes: - Fix dumping and exporting multiple files from the CLI (Richard Hughes) - Watch files before loading them into the builder (Philip Withnall) - Fix potential double free when filtering by language (Sergio Costas Rodriguez) Version 0.3.9 ~~~~~~~~~~~~~ Released: 2022-05-24 Bugfixes: - Fix the crash for when the root tree has no children (Richard Hughes) - Fix the crash when getting the element for the [empty] root (Richard Hughes) - Install xb-tool into bindir (Richard Hughes) Version 0.3.8 ~~~~~~~~~~~~~ Released: 2022-03-22 Bugfixes: - Fix building on macOS (Bobby Rong) Version 0.3.7 ~~~~~~~~~~~~~ Released: 2022-02-16 Bugfixes: - Ensure reproducible results when importing a node (Richard Hughes) - Ignore all hidden files when using _WATCH_DIRECTORY (Richard Hughes) - Show the value bindings when using XB_SILO_PROFILE_FLAG_XPATH (Richard Hughes) - Use the correct lookup method for the python3 script interpreter (Eli Schwartz) Version 0.3.6 ~~~~~~~~~~~~~ Released: 2021-12-06 Bugfixes: - Ensure _IS_TOKENIZED is set if tokens are added manually (Richard Hughes) - Ensure we never add too many tokens to the silo (Richard Hughes) Version 0.3.5 ~~~~~~~~~~~~~ Released: 2021-12-01 Bugfixes: - Put tail after the node when exporting XbBuilderNode (Matthias Klumpp) - Remove the G_ALIGNOF checks to fix compile with old GLib versions (Richard Hughes) Version 0.3.4 ~~~~~~~~~~~~~ Released: 2021-11-29 New Features: - Add a flag to require the XbBuilderSource to have no siblings (Richard Hughes) - Add iterator for XbNode attributes and children (Matthias Klumpp) - Allow removing XbBuilderNode text (Richard Hughes) - Allow stripping builder node inner text (Richard Hughes) - Include records in the symbol export list (Richard Hughes) - Use -Dcli=false to reduce the install size (Richard Hughes) Version 0.3.3 ~~~~~~~~~~~~~ Released: 2021-10-06 New Features: - Add support for LZMA decompression (Richard Hughes) Bugfixes: - Add locking for file monitors (Philip Withnall) - Clarify GMainContext usage and signal emission (Philip Withnall) - Modernise property declaration (Philip Withnall) Version 0.3.2 ~~~~~~~~~~~~~ Released: 2021-05-24 Bugfixes: - Respect XB_BUILDER_NODE_FLAG_IGNORE when exporting (Richard Hughes) - Use the chosen chunk size when reading from a stream (Richard Hughes) - Use the correct pkgconfig export package name in the GIR file (Richard Hughes) Version 0.3.1 ~~~~~~~~~~~~~ Released: 2021-05-06 New Features: - Use stack-allocated XbStack instances for running a machine (Philip Withnall) Bugfixes: - Always run xb-tool queries with the optimizer (Richard Hughes) - Do not use g_file_replace_contents for win32 (Richard Hughes) - Ensure tokens handling works when XbOpcode is not zero-inited (Philip Withnall) Version 0.3.0 ~~~~~~~~~~~~~ Released: 2021-03-12 New Features: - Add a new object to contain query context data (Philip Withnall) - Allow collapsing empty XML tags if no children or text (Aleksander Morgado) - Allow marking elements as tokenized from xb-tool (Richard Hughes) - Allow optimized searching when comparing tokens (Richard Hughes) - Support mmap()ing the source file to get bytes (Philip Withnall) - Support query caching with xb_silo_lookup_query() (Philip Withnall) - Support UTF-8 for upper-case() and lower-case() (Richard Hughes) - Use g_str_match_string() for non-ASCII search queries (Richard Hughes) - Write search tokens into the built silo (Richard Hughes) Bugfixes: - Cancel the GFileMonitor before unreffing it (Iain Lane) - Do not allocate a 128Mb buffer for each xb_builder_source_ctx_get_bytes() (Richard Hughes) - Do not error when creating a query if the element doesn’t exist (Philip Withnall) - Fix a parse failure for a double comment (Richard Hughes) - Fix various errors or missing annotations in docs (Philip Withnall) - Make handling of single-result queries more robust (Philip Withnall) Version 0.2.1 ~~~~~~~~~~~~~ Released: 2020-09-07 Bugfixes: - Do not assume g_content_type_guess() always returns valid results (Richard Hughes) - Make the build reproducible (Richard Hughes) - Revert "Do not show a critical warning for invalid XML" (Richard Hughes) - Update the header location to reflect the new API (Richard Hughes) Version 0.2.0 ~~~~~~~~~~~~~ Released: 2020-08-18 Important: - This release breaks API and ABI and bumps the version of libxmlb.so and so packages that depend on this library (e.g. fwupd or gnome-software) will need to be rebuilt at the same time. New Features: - Add the missing TEXT:INTE XPath support (Richard Hughes) - Add variant of xb_silo_query_with_root() avoiding XbNode creation (Philip Withnall) - Add XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY flag (Philip Withnall) - Allow specifying the node cache behaviour for the query (Richard Hughes) Bugfixes: - Avoid recursion when setting flags if possible (Philip Withnall) - Avoid using weak pointers when building the silo (Philip Withnall) - Change the default value for the node cache (Richard Hughes) - Do not allocate opcodes individually (Philip Withnall) - Do not show a critical warning for invalid XML (Richard Hughes) - Do not unconditionally create GTimer objects (Philip Withnall) - Do not use the node cache when building indexes (Richard Hughes) - Lazy load more arrays to reduce RSS usage (Philip Withnall) - Report silo versions when versions mismatch (Robert Ancell) Version 0.1.15 ~~~~~~~~~~~~~~ Released: 2020-03-04 New Features: - Add xb_builder_source_add_simple_adapter (Daniel Campello) - Allow reversing the query results (Richard Hughes) Version 0.1.14 ~~~~~~~~~~~~~~ Released: 2019-12-03 New Features: - Add MinGW Windows 64 builds to CI (Richard Hughes) Bugfixes: - Allow compiling without gio-unix (Richard Hughes) - Do not use libuuid (Richard Hughes) - Do not unconditionally use -fstack-protector-strong (Richard Hughes) - Ignore adaptors added with xb_builder_source_add_converter() (Richard Hughes) Version 0.1.13 ~~~~~~~~~~~~~~ Released: 2019-10-17 New Features: - Export xb_silo_query_full() (Richard Hughes) Bugfixes: - Show the XPath that was used in the query in the error message (Richard Hughes) Version 0.1.12 ~~~~~~~~~~~~~~ Released: 2019-09-27 New Features: - Add helper functions to get the first and last child XbBuilerNode (Richard Hughes) - Add xb_node_transmogrify to allow changing XML format (Richard Hughes) - Support 'tail' XML data in the stored silo (Richard Hughes) Bugfixes: - Do not escape a single quote with ' (Richard Hughes) - Don't invalidate the silo for a GIO temp file (Richard Hughes) - Fix up two memory leaks if using libxmlb from an introspected binding (Richard Hughes) Version 0.1.11 ~~~~~~~~~~~~~~ Released: 2019-07-15 New Features: - Add xb_node_query_first_full() convenience helper (Richard Hughes) - Rebuild the XbMachine parser to support 'and' and 'or' predicates (Richard Hughes) - Verify we never break the ABI (Mathieu Bridon) Bugfixes: - Conditionalize installation of test data (Mathieu Bridon) - Generate a GUID of zero for no silo data (Richard Hughes) - Run CI jobs in parallel (Mathieu Bridon) Version 0.1.10 ~~~~~~~~~~~~~~ Released: 2019-05-16 Bugfixes: - Do not mistake gzipped files as being application/x-zerosize content type (Richard Hughes) - Fix running the installed tests with no checkout directory (Richard Hughes) Version 0.1.9 ~~~~~~~~~~~~~ Released: 2019-05-07 Bugfixes: - Correctly implement building a silo with _SINGLE_LANG set (Richard Hughes) Version 0.1.8 ~~~~~~~~~~~~~ Released: 2019-03-26 New Features: - Add some installed tests (Richard Hughes) Bugfixes: - Always add all children when importing parent-less XML data (Richard Hughes) Version 0.1.7 ~~~~~~~~~~~~~ Released: 2019-03-08 New Features: - Add XB_BUILDER_COMPILE_FLAG_IGNORE_GUID (Richard Hughes) - Allow nesting XbBuilderSource content type handlers (Richard Hughes) Bugfixes: - Correct return type for xb_builder_compile() (Mario Limonciello) - Increase the amount of time for the inotify event to happen (Richard Hughes) - Only run the speed tests when using '-m perf' (Richard Hughes) Version 0.1.6 ~~~~~~~~~~~~~ Released: 2018-12-30 New Features: - Allow controlling how the XbQuery is parsed (Richard Hughes) Version 0.1.5 ~~~~~~~~~~~~~ Released: 2018-11-21 New Features: - Add xb_builder_node_export() (Richard Hughes) Bugfixes: - Do not start a GIO session bus when using xb-tool (Richard Hughes) - Ignore calls to xb_silo_query_build_index() with no results (Richard Hughes) - Lazy load the stemmer when required (Richard Hughes) Version 0.1.4 ~~~~~~~~~~~~~ Released: 2018-11-08 New Features: - Add support for bound variables (Richard Hughes) - Add support for indexed strings (Richard Hughes) - Optionally optimize predicates (Richard Hughes) - Split out the XPath query logic into XbQuery (Richard Hughes) - Support XPath method to do a Porter stem operation on text (Richard Hughes) Bugfixes: - Don't hardcode the path of python3 (Ting-Wei Lan) - Only attempt to pass TEXT opcodes for upper-case() (Richard Hughes) - Revert the optional text space saving feature (Richard Hughes) - Use INTE:INTE for comparison where available (Richard Hughes) Version 0.1.3 ~~~~~~~~~~~~~ Released: 2018-10-22 New Features: - Add XbBuilderFixup (Richard Hughes) - Add xb_builder_node_get_attr_as_uint() (Richard Hughes) - Add xb_builder_node_get_text_as_uint() (Richard Hughes) - Add xb_builder_node_sort_children() (Richard Hughes) Bugfixes: - Allow being used as a meson subproject (Jan Tojnar) - Fix compiling on Ubuntu Xenial (Richard Hughes) - Switch from GPtrArray to XbStack for performance reasons (Richard Hughes) - Update meson relro checks (Jan Tojnar) Version 0.1.2 ~~~~~~~~~~~~~ Released: 2018-10-16 New Features: - Add support for XPath string-length() (Richard Hughes) - Add xb_builder_source_load_bytes() (Richard Hughes) - Add xb_silo_export_file() (Richard Hughes) - Add xb_string_append_union() (Richard Hughes) - Add xb_string_escape() (Richard Hughes) Bugfixes: - Allow the use as meson subproject (Jan Tojnar) - Do not query with the first child (Richard Hughes) - Fix a crash when using xb_builder_node_set_text() in a fixup (Richard Hughes) - Only run the XbBuilderSourceConverterFunc if the silo needs rebuilding (Richard Hughes) - Return an error when the XPath predicate has invalid syntax (Richard Hughes) Version 0.1.1 ~~~~~~~~~~~~~ Released: 2018-10-11 New Features: - Add support for XPath starts-with() and ends-with() (Richard Hughes) - Add support for number() and text() type conversion (Richard Hughes) - Add support for predicate not() (Richard Hughes) - Add xb_silo_invalidate() (Richard Hughes) - Export XbMachine and XbOpcode (Richard Hughes) Bugfixes: - Add the XbBuilderSource info keys to the generated GUID (Richard Hughes) - Add the XbBuilderSourceNodeFunc ID to the generated GUID (Richard Hughes) - Add the source prefix to the generated GUID (Richard Hughes) - Do not use GNode when compiling the blob (Richard Hughes) - Ignore invalid arguments if OR queries have other sections to parse (Richard Hughes) - Load back the saved new silo to get a mmapped version (Richard Hughes) - Support XPath wildcard nodes with predicates (Richard Hughes) - Support some relative parent queries (Richard Hughes) - Use the time::changed-usec if set in the GUID (Richard Hughes) Version 0.1.0 ~~~~~~~~~~~~~ Released: 2018-10-05 Notes: - A library for querying compressed XML metadata. libxmlb-0.3.22/README.md000066400000000000000000000130571476425255200145300ustar00rootroot00000000000000libxmlb ======= [![Coverity Scan Build Status](https://scan.coverity.com/projects/17055/badge.svg)](https://scan.coverity.com/projects/17055) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/hughsie/libxmlb/badge)](https://securityscorecards.dev/viewer/?uri=github.com/hughsie/libxmlb) Introduction ------------ XML is slow to parse and strings inside the document cannot be memory mapped as they do not have a trailing NUL char. The libxmlb library takes XML source, and converts it to a structured binary representation with a deduplicated string table -- where the strings have the NULs included. This allows an application to mmap the binary XML file, do an XPath query and return some strings without actually parsing the entire document. This is all done using (almost) zero allocations and no actual copying of the binary data. As each node in the binary XML file encodes the 'next' node at the same level it makes skipping whole subtrees trivial. A 10Mb binary XML file can be loaded from disk **and** queried in less than a few milliseconds. The binary XML is not supposed to be small. It's usually about half the size of the text XML data where a lot of the tag content is duplicated, but can actually be larger than the original XML file. This isn't important; the fast query speed and the ability to mmap strings without copies more than makes up for the larger on-disk size. If you want to compress your XML, this library probably isn't for you -- just use gzip -- it gives you an almost a perfect compression ratio for data like this. For example: $ xb-tool compile fedora.xmlb fedora.xml.gz $ du -h fedora.xml* 12M fedora.xmlb 3.6M fedora.xml.gz $ xb-tool query fedora.xmlb "components/component[@type=desktop]/id[text()=firefox.desktop]" RESULT: firefox.desktop real 0m0.011s user 0m0.010s sys 0m0.001s XPath ===== This library only implements a tiny subset of XPath. See the examples for the full list, but it's basically restricted to element_name, attributes and text. We will use the following XML document in the examples below. Harry Potter 29.99 Learning XML 39.95 Selecting Nodes --------------- XPath uses path expressions to select nodes in an XML document. The only thing that libxmlb can return are nodes. | Example | Description | Supported | | --- | --- | --- | | `/bookstore` | Returns the root bookstore element | ✔ | | `/bookstore/book` | Returns all `book` elements | ✔ | | `//book` | Returns books no matter where they are | ✖ | | `bookstore//book` | Returns books that are descendant of `bookstore` | ✖ | | `@lang` | Returns attributes that are named `lang` | ✖ | | `/bookstore/.` | Returns the `bookstore` node | ✖ | | `/bookstore/book/*` | Returns all `title` and `price` nodes of each `book` node | ✔ | | `/bookstore/book/child::*` | Returns all `title` and `price` nodes of each `book` node | ✔ | | `/bookstore/book/title/..` | Returns the `book` nodes with a title | ✔ | | `/bookstore/book/parent::*` | Returns `bookstore`, the parent of `book` | ✔ | | `/bookstore/book/parent::bookstore` | Returns the parent `bookstore` of `book` | ✖ | Predicates ---------- Predicates are used to find a specific node or a node that contains a specific value. Predicates are always embedded in square brackets. | Example | Description | Supported | | --- | --- | --- | | `/bookstore/book[1]` | Returns the first book element | ✔ | | `/bookstore/book[first()]` | Returns the first book element | ✔ | | `/bookstore/book[last()]` | Returns the last book element | ✔ | | `/bookstore/book[last()-1]` | Returns the last but one book element | ✖ | | `/bookstore/book[position()<3]` | Returns the first two books | ✔ | | `/bookstore/book[upper-case(text())=='HARRY POTTER']` | Returns the first book | ✔ | | `/bookstore/book[@percentage>=90]` | Returns the book with `>=` 90% completion | ✔ | | `/bookstore/book/title[@lang]` | Returns titles with an attribute named `lang` | ✔ | | `/bookstore/book/title[@lang='en']` | Returns titles that have a `lang`equal `en` | ✔ | | `/bookstore/book/title[@lang!='en']` | Returns titles that have a `lang` not equal `en` | ✔ | | `/bookstore/book/title[@lang<='zz_ZZ']` | Returns titles that `lang` <= `zz_ZZ` | ✔ | | `/bookstore/book[price>35.00]` | Returns the books with a price greater than 35 | ✖ | | `/bookstore/book[price>35.00]/title` | Returns the titles that have a price greater than 35 | ✖ | | `/bookstore/book/title[text()='Learning XML']` | Returns the book node with matching content | ✔ | Compilation ---------- libxmlb is a standard meson project. It can be compiled using the following basic steps: ``` # meson build # ninja -C build # ninja -C build install # ldconfig ``` This will by default install the library into `/usr/local`. On some Linux distributions you may need to configure the linker path in `/etc/ld.so.conf` to be able to locate it. The call to `ldconfig` is needed to refresh the linker cache. `meson build` has options that can be used to disable certain features, e.g. ``` # meson build -Dintrospection=false -Dgtkdoc=false -Dcli=false ``` will remove support for GObject introspection, Gtk documentation, and will only build the library without the command line tool. As a result, fewer libraries are needed for building and running the project. libxmlb-0.3.22/RELEASE000066400000000000000000000013261476425255200142500ustar00rootroot00000000000000libxmlb Release Notes 1. Write NEWS entries for libxmlb in the same format as usual. git shortlog 0.3.21.. | grep -i -v trivial | grep -v Merge > NEWS.new Version 0.3.22 ~~~~~~~~~~~~~~ Released: 2025-xx-xx New Features: Bugfixes: Commit changes to git: # MAKE SURE THESE ARE CORRECT export release_ver="0.3.22" git commit -a -m "Release libxmlb ${release_ver}" --no-verify git tag -s -f -m "Release libxmlb ${release_ver}" "${release_ver}" ninja dist git push --tags git push gpg -b -a meson-dist/libxmlb-${release_ver}.tar.xz Upload release artifacts via https://github.com/hughsie/libxmlb/tags Do post release version bump in meson.build git commit -a -m "trivial: post release version bump" --no-verify git push libxmlb-0.3.22/SECURITY.md000066400000000000000000000006021476425255200150320ustar00rootroot00000000000000# Security Policy ## Supported Versions | Version | Supported | | ------- | ------------------ | | 0.3.x | :white_check_mark: | | 0.2.x | :x: | | 0.1.x | :x: | ## Reporting a Vulnerability We have enabled private reporting in GitHub, so please [follow these steps](https://github.com/hughsie/libxmlb/security) to report vulnerabilities. libxmlb-0.3.22/contrib/000077500000000000000000000000001476425255200147035ustar00rootroot00000000000000libxmlb-0.3.22/contrib/ci/000077500000000000000000000000001476425255200152765ustar00rootroot00000000000000libxmlb-0.3.22/contrib/ci/Dockerfile-debian000066400000000000000000000010001476425255200204770ustar00rootroot00000000000000FROM debian:unstable RUN apt-get update -qq RUN apt-get install -yq --no-install-recommends \ gcovr \ gobject-introspection \ gtk-doc-tools \ libgirepository1.0-dev \ libglib2.0-bin \ libglib2.0-dev \ libstemmer-dev \ ninja-build \ python3-pip \ python3-setuptools \ python3-wheel \ shared-mime-info \ liblzma-dev \ libzstd-dev \ uuid-dev \ pkg-config # Meson is too old in unstable, and that won't change until Buster is released RUN pip3 install meson --break-system-packages WORKDIR /build libxmlb-0.3.22/contrib/ci/Dockerfile-fedora000066400000000000000000000004421476425255200205260ustar00rootroot00000000000000FROM fedora:40 RUN dnf -y update RUN dnf -y install \ diffutils \ gcovr \ xz-devel \ libzstd-devel \ git-core \ glib2-devel \ gobject-introspection-devel \ gtk-doc \ libabigail \ libstemmer-devel \ libuuid-devel \ meson \ shared-mime-info \ redhat-rpm-config WORKDIR /build libxmlb-0.3.22/contrib/ci/Dockerfile-fedora-w64000066400000000000000000000003751476425255200211510ustar00rootroot00000000000000FROM fedora:40 RUN dnf -y update RUN dnf -y install \ diffutils \ gcc \ git-core \ meson \ mingw64-xz-libs \ mingw64-zstd \ mingw64-gcc \ mingw64-glib2 \ mingw64-pkg-config \ redhat-rpm-config \ shared-mime-info \ wine-core WORKDIR /build libxmlb-0.3.22/contrib/ci/abidiff.suppr000066400000000000000000000005411476425255200177550ustar00rootroot00000000000000[suppress_type] type_kind = enum changed_enumerators = XB_BUILDER_NODE_FLAG_LAST,XB_BUILDER_SOURCE_FLAG_LAST,XB_BUILDER_COMPILE_FLAG_LAST,XB_MACHINE_DEBUG_FLAG_LAST,XB_MACHINE_PARSE_FLAG_LAST,XB_NODE_EXPORT_FLAG_LAST,XB_OPCODE_FLAG_LAST,XB_OPCODE_KIND_LAST,XB_SILO_QUERY_KIND_LAST,XB_QUERY_FLAG_LAST,XB_SILO_LOAD_FLAG_LAST,XB_SILO_PROFILE_FLAG_LAST libxmlb-0.3.22/contrib/ci/build-debian.sh000077500000000000000000000003141476425255200201520ustar00rootroot00000000000000#!/bin/sh set -e export LC_ALL=C.UTF-8 mkdir -p build && cd build rm -rf * meson .. \ -Dgtkdoc=true \ -Dtests=true $@ ninja -v || bash ninja test -v DESTDIR=/tmp/install-ninja ninja install cd .. libxmlb-0.3.22/contrib/ci/build-fedora-w64.sh000077500000000000000000000005701476425255200206120ustar00rootroot00000000000000#!/bin/sh set -e export LC_ALL=C.UTF-8 mkdir -p build && cd build rm -rf * meson .. \ --cross-file=../contrib/mingw64.cross \ -Dintrospection=false \ -Dgtkdoc=false \ -Dtests=true $@ ninja -v || bash wine reg add "HKEY_CURRENT_USER\Environment" /v PATH /d /usr/x86_64-w64-mingw32/sys-root/mingw/bin ninja test -v DESTDIR=/tmp/install-ninja ninja install cd .. libxmlb-0.3.22/contrib/ci/build-fedora.sh000077500000000000000000000004241476425255200201720ustar00rootroot00000000000000#!/bin/sh set -e export LC_ALL=C.UTF-8 mkdir -p build && cd build rm -rf * meson .. \ -Db_coverage=true \ -Dgtkdoc=true \ -Dtests=true $@ ninja -v || bash ninja test -v ninja coverage-text cat meson-logs/coverage.txt DESTDIR=/tmp/install-ninja ninja install cd .. libxmlb-0.3.22/contrib/ci/check-abi000077500000000000000000000070041476425255200170330ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import contextlib import os import shutil import subprocess import sys def format_title(title): box = { "tl": "╔", "tr": "╗", "bl": "╚", "br": "╝", "h": "═", "v": "║", } hline = box["h"] * (len(title) + 2) return "\n".join( [ f"{box['tl']}{hline}{box['tr']}", f"{box['v']} {title} {box['v']}", f"{box['bl']}{hline}{box['br']}", ] ) def rm_rf(path): try: shutil.rmtree(path) except FileNotFoundError: pass def sanitize_path(name): return name.replace("/", "-") def get_current_revision(): revision = subprocess.check_output( ["git", "rev-parse", "--abbrev-ref", "HEAD"], encoding="utf-8" ).strip() if revision == "HEAD": # This is a detached HEAD, get the commit hash revision = ( subprocess.check_output(["git", "rev-parse", "HEAD"]) .strip() .decode("utf-8") ) return revision @contextlib.contextmanager def checkout_git_revision(revision): current_revision = get_current_revision() subprocess.check_call(["git", "checkout", "-q", revision]) try: yield finally: subprocess.check_call(["git", "checkout", "-q", current_revision]) def build_install(revision): build_dir = "_build" dest_dir = os.path.abspath(sanitize_path(revision)) print( format_title(f"# Building and installing {revision} in {dest_dir}"), end="\n\n", flush=True, ) with checkout_git_revision(revision): rm_rf(build_dir) rm_rf(revision) subprocess.check_call( [ "meson", build_dir, "--prefix=/usr", "--libdir=lib", "-Db_coverage=false", "-Dgtkdoc=false", "-Dtests=false", ] ) subprocess.check_call(["ninja", "-v", "-C", build_dir]) subprocess.check_call( ["ninja", "-v", "-C", build_dir, "install"], env={"DESTDIR": dest_dir} ) return dest_dir def compare(old_tree, new_tree): print(format_title(f"# Comparing the two ABIs"), end="\n\n", flush=True) old_headers = os.path.join(old_tree, "usr", "include") old_lib = os.path.join(old_tree, "usr", "lib", "libxmlb.so") new_headers = os.path.join(new_tree, "usr", "include") new_lib = os.path.join(new_tree, "usr", "lib", "libxmlb.so") subprocess.check_call( [ "abidiff", "--headers-dir1", old_headers, "--headers-dir2", new_headers, "--drop-private-types", "--suppressions", "contrib/ci/abidiff.suppr", "--fail-no-debug-info", "--no-added-syms", old_lib, new_lib, ] ) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("old", help="the previous revision, considered the reference") parser.add_argument("new", help="the new revision, to compare to the reference") args = parser.parse_args() if args.old == args.new: print("Let's not waste time comparing something to itself") sys.exit(0) old_tree = build_install(args.old) new_tree = build_install(args.new) try: compare(old_tree, new_tree) except Exception: sys.exit(1) print(f"Hurray! {args.old} and {args.new} are ABI-compatible!") libxmlb-0.3.22/contrib/ci/check-license.py000077500000000000000000000023171476425255200203530ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright 2021 Richard Hughes # Copyright 2021 Mario Limonciello # # SPDX-License-Identifier: LGPL-2.1-or-later import glob import os import sys def __get_license(fn: str) -> str: with open(fn, "r") as f: for line in f.read().split("\n"): if line.find("SPDX-License-Identifier:") > 0: return line.split(":")[1] return "" def test_files() -> int: rc: int = 0 build_dirs = [os.path.dirname(cf) for cf in glob.glob("**/config.h")] for fn in glob.glob("**/*.[c|h|py|sh]", recursive=True): if "meson-private" in fn: continue if os.path.isdir(fn): continue if fn.startswith(tuple(build_dirs)): continue if fn.startswith("subprojects"): continue lic = __get_license(fn) if not lic: print("{} does not specify a license".format(fn)) rc = 1 continue if not "GPL" in lic: print("{} does not contain LGPL or GPL ({})".format(fn, lic)) rc = 1 continue return rc if __name__ == "__main__": # all done! sys.exit(test_files()) libxmlb-0.3.22/contrib/ci/check-null-false-returns.py000077500000000000000000000170571476425255200225020ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring,too-many-branches # pylint: disable=too-many-statements,too-many-return-statements,too-few-public-methods # # Copyright 2021 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1-or-later import glob import sys from typing import List def _tokenize(line: str) -> List[str]: # remove whitespace line = line.strip() line = line.replace("\t", "") line = line.replace(";", "") # find value line = line.replace(" ", "|") line = line.replace(",", "|") line = line.replace(")", "|") line = line.replace("(", "|") # return empty tokens tokens = [] for token in line.rsplit("|"): if token: tokens.append(token) return tokens class ReturnValidator: def __init__(self): self.warnings: List[str] = [] # internal state self._fn = None self._line_num = None self._value = None self._nret = None self._rvif = None self._line = None @property def _tokens(self) -> List[str]: return _tokenize(self._line) @property def _value_relaxed(self) -> str: if self._value in ["0x0", "0x00", "0x0000"]: return "0" if self._value in ["0xffffffff"]: return "G_MAXUINT32" if self._value in ["0xffff"]: return "G_MAXUINT16" if self._value in ["0xff"]: return "G_MAXUINT8" if self._value in ["G_SOURCE_REMOVE"]: return "FALSE" if self._value in ["G_SOURCE_CONTINUE"]: return "TRUE" return self._value def _test_rvif(self) -> None: # parse "g_return_val_if_fail (SOMETHING (foo), NULL);" self._value = self._tokens[-1] # enumerated enum, so ignore if self._value.find("_") != -1: return # is invalid if self._rvif and self._value_relaxed not in self._rvif: self.warnings.append( "{} line {} got {}, expected {}".format( self._fn, self._line_num, self._value, ", ".join(self._rvif) ) ) def _test_return(self) -> None: # parse "return 0x0;" self._value = self._tokens[-1] # is invalid if self._nret and self._value_relaxed in self._nret: self.warnings.append( "{} line {} got {}, which is not valid".format( self._fn, self._line_num, self._value ) ) def parse(self, fn: str) -> None: self._fn = fn with open(fn) as f: self._rvif = None self._nret = None self._line_num = 0 for line in f.readlines(): self._line_num += 1 line = line.rstrip() if not line: continue if line.endswith("\\"): continue if line.endswith("&&"): continue self._line = line idx = line.find("g_return_val_if_fail") if idx != -1: self._test_rvif() continue idx = line.find("return") if idx != -1: # continue if len(self._tokens) == 2: self._test_return() continue # not a function header if line[0] in ["#", " ", "\t", "{", "}", "/"]: continue # label if line.endswith(":"): continue # remove prefixes if line.startswith("static"): line = line[7:] if line.startswith("inline"): line = line[7:] # a pointer if line.endswith("*"): self._rvif = ["NULL"] self._nret = ["FALSE"] continue # not a leading line if line.find(" ") != -1: continue # a type we know if line in ["void"]: self._rvif = [] self._nret = [] continue if line in ["gpointer"]: self._rvif = ["NULL"] self._nret = ["FALSE"] continue if line in ["gboolean"]: self._rvif = ["TRUE", "FALSE"] self._nret = ["NULL", "0"] continue if line in ["guint32"]: self._rvif = ["0", "G_MAXUINT32"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["GQuark", "GType"]: self._rvif = ["0"] self._nret = ["NULL", "0", "TRUE", "FALSE"] continue if line in ["guint64"]: self._rvif = ["0", "G_MAXUINT64"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["guint16"]: self._rvif = ["0", "G_MAXUINT16"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["guint8"]: self._rvif = ["0", "G_MAXUINT8"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gint64"]: self._rvif = ["0", "-1", "G_MAXINT64"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gint32"]: self._rvif = ["0", "-1", "G_MAXINT32"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gint16"]: self._rvif = ["0", "-1", "G_MAXINT16"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gint8"]: self._rvif = ["0", "-1", "G_MAXINT8"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gint", "int"]: self._rvif = ["0", "-1", "G_MAXINT"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["guint"]: self._rvif = ["0", "G_MAXUINT"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gulong"]: self._rvif = ["0", "G_MAXLONG"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gsize", "size_t"]: self._rvif = ["0", "G_MAXSIZE"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gssize", "ssize_t"]: self._rvif = ["0", "-1", "G_MAXSSIZE"] self._nret = ["NULL", "TRUE", "FALSE"] continue # print('unknown return type {}'.format(line)) self._rvif = None self._nret = None def test_files(): # test all C source files validator = ReturnValidator() for fn in glob.glob("**/*.c", recursive=True): validator.parse(fn) for warning in validator.warnings: print(warning) return 1 if validator.warnings else 0 if __name__ == "__main__": # all done! sys.exit(test_files()) libxmlb-0.3.22/contrib/libxmlb.spec.in000066400000000000000000000047771476425255200176340ustar00rootroot00000000000000%global glib2_version 2.45.8 %define alphatag #ALPHATAG# Summary: Library for querying compressed XML metadata Name: libxmlb Version: #VERSION# Release: 0.#BUILD#%{?alphatag}%{?dist} License: LGPL-2.1-or-later URL: https://github.com/hughsie/libxmlb Source0: http://people.freedesktop.org/~hughsient/releases/%{name}-%{version}.tar.xz BuildRequires: glib2-devel >= %{glib2_version} BuildRequires: gtk-doc BuildRequires: libstemmer-devel BuildRequires: meson BuildRequires: gobject-introspection-devel BuildRequires: xz-devel BuildRequires: libzstd-devel %if 0%{?rhel} == 7 BuildRequires: python36-setuptools %else BuildRequires: python3-setuptools %endif # needed for the self tests BuildRequires: shared-mime-info Requires: glib2%{?_isa} >= %{glib2_version} Requires: shared-mime-info %description XML is slow to parse and strings inside the document cannot be memory mapped as they do not have a trailing NUL char. The libxmlb library takes XML source, and converts it to a structured binary representation with a deduplicated string table -- where the strings have the NULs included. This allows an application to mmap the binary XML file, do an XPath query and return some strings without actually parsing the entire document. This is all done using (almost) zero allocations and no actual copying of the binary data. %package devel Summary: Development package for %{name} Requires: %{name}%{?_isa} = %{version}-%{release} %description devel Files for development with %{name}. %package tests Summary: Files for installed tests %description tests Executable and data files for installed tests. %prep %setup -q %build %meson \ -Dgtkdoc=true \ -Dtests=true %meson_build %check %meson_test %install %meson_install %files %doc README.md %license LICENSE %{_bindir}/xb-tool %{_mandir}/man1/xb-tool.1* %dir %{_libdir}/girepository-1.0 %{_libdir}/girepository-1.0/Xmlb-2.0.typelib %{_libdir}/libxmlb.so.2* %files devel %dir %{_datadir}/gir-1.0 %{_datadir}/gir-1.0/Xmlb-2.0.gir %dir %{_datadir}/gtk-doc %dir %{_datadir}/gtk-doc/html %{_datadir}/gtk-doc/html/libxmlb %{_includedir}/libxmlb-2 %{_libdir}/libxmlb.so %{_libdir}/pkgconfig/xmlb.pc %files tests %dir %{_libexecdir}/installed-tests/libxmlb %{_libexecdir}/installed-tests/libxmlb/xb-self-test %{_libexecdir}/installed-tests/libxmlb/test.* %dir %{_datadir}/installed-tests/libxmlb %{_datadir}/installed-tests/libxmlb/libxmlb.test %changelog * #LONGDATE# Richard Hughes #VERSION#-0.#BUILD##ALPHATAG# - Update from git libxmlb-0.3.22/contrib/mingw64.cross000066400000000000000000000005151476425255200172520ustar00rootroot00000000000000[binaries] c = '/usr/bin/x86_64-w64-mingw32-gcc' cpp = '/usr/bin/x86_64-w64-mingw32-g++' ar = '/usr/bin/x86_64-w64-mingw32-ar' strip = '/usr/bin/x86_64-w64-mingw32-strip' pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config' exe_wrapper = 'wine' [host_machine] system = 'windows' cpu_family = 'x86_64' cpu = 'i686' endian = 'little' libxmlb-0.3.22/contrib/reformat-code.py000077500000000000000000000046661476425255200200230ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright 2017 Dell Inc. # # SPDX-License-Identifier: LGPL-2.1-or-later # import os import sys import subprocess import argparse CLANG_DIFF_FORMATTERS = [ "clang-format-diff-11", "clang-format-diff-13", "clang-format-diff", ] def parse_args(): parser = argparse.ArgumentParser( description="Reformat C code to match project style", epilog="Call with no argument to reformat uncommitted code.", ) parser.add_argument( "commit", nargs="*", default="", help="Reformat all changes since this commit" ) parser.add_argument( "--debug", action="store_true", help="Display all launched commands" ) return parser.parse_args() def select_clang_version(formatters): for formatter in formatters: try: ret = subprocess.check_call( [formatter, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if ret == 0: return formatter except FileNotFoundError: continue print("No clang formatter installed") sys.exit(1) ## Entry Point ## if __name__ == "__main__": args = parse_args() base = os.getenv("GITHUB_BASE_REF") if base: base = "origin/%s" % base else: if args.commit: base = args.commit[0] else: base = "HEAD" cmd = ["git", "describe", base] if args.debug: print(cmd) ret = subprocess.run(cmd, capture_output=True) if ret.returncode: if args.debug: print(ret.stderr) base = "HEAD" print("Reformatting code against %s" % base) formatter = select_clang_version(CLANG_DIFF_FORMATTERS) cmd = ["git", "diff", "-U0", base] if args.debug: print(cmd) ret = subprocess.run(cmd, capture_output=True, text=True) if ret.returncode: print("Failed to run %s\n%s" % (cmd, ret.stderr.strip())) sys.exit(1) cmd = [formatter, "-p1"] if args.debug: print(cmd) ret = subprocess.run(cmd, input=ret.stdout, capture_output=True, text=True) if ret.returncode: print("Failed to run %s\n%s" % (cmd, ret.stderr.strip())) sys.exit(1) cmd = ["patch", "-p0"] if args.debug: print(cmd) ret = subprocess.run(cmd, input=ret.stdout, capture_output=True, text=True) if ret.returncode: print("Failed to run %s\n%s" % (cmd, ret.stderr.strip())) sys.exit(1) sys.exit(0) libxmlb-0.3.22/contrib/setup000077500000000000000000000017741476425255200160020ustar00rootroot00000000000000#!/bin/bash -e # Setup the repository and local system for development cd "$(dirname "$0")/.." rename_branch() { OLD=master NEW=main if git log $OLD >/dev/null 2>&1 && git remote get-url origin 2>&1 | grep fwupd/fwupd.git >/dev/null 2>&1; then read -p "Rename existing $OLD branch to $NEW? (y/N) " question if [ "$question" = "y" ]; then git branch -m $OLD $NEW git fetch origin git branch -u origin/$NEW $NEW git remote set-head origin -a fi fi } setup_git() { echo "Configuring git environment" git config include.path ../.gitconfig } install_pip() { package=$1 python3 -m pip install $package } setup_precommit() { echo "Configuring pre-commit hooks" python3 -m venv venv source venv/bin/activate install_pip pre-commit pre-commit install } #always setup pre-commit setup_precommit #always setup git environment setup_git #if interactive if [ -t 2 ]; then rename_branch fi libxmlb-0.3.22/data/000077500000000000000000000000001476425255200141545ustar00rootroot00000000000000libxmlb-0.3.22/data/fuzzing-src/000077500000000000000000000000001476425255200164355ustar00rootroot00000000000000libxmlb-0.3.22/data/fuzzing-src/appdata.xml000066400000000000000000000016241476425255200205740ustar00rootroot00000000000000 test.firmware ColorHug2 Firmware

New features!

2082b5e0 fwupd http://com/ CC0-1.0 GPL-2.0+ richard Hughski

stable:

  • Quicker
libxmlb-0.3.22/data/fuzzing-xpath/000077500000000000000000000000001476425255200167725ustar00rootroot00000000000000libxmlb-0.3.22/data/fuzzing-xpath/attr.txt000066400000000000000000000000371476425255200205050ustar00rootroot00000000000000component[@type='firmware']/id libxmlb-0.3.22/data/fuzzing-xpath/down-then-up-then-down.txt000066400000000000000000000000461476425255200237610ustar00rootroot00000000000000component/url[@type='homepage']/../id libxmlb-0.3.22/data/fuzzing-xpath/or.txt000066400000000000000000000001001476425255200201420ustar00rootroot00000000000000component/id[text()='foo']|component/id[text()='test.firmware'] libxmlb-0.3.22/data/fuzzing-xpath/search.txt000066400000000000000000000000351476425255200207760ustar00rootroot00000000000000component/id[text()~='firm'] libxmlb-0.3.22/data/fuzzing-xpath/simple.txt000066400000000000000000000000161476425255200210210ustar00rootroot00000000000000/component/id libxmlb-0.3.22/data/fuzzing-xpath/text.txt000066400000000000000000000000451476425255200205160ustar00rootroot00000000000000component/id[text()='test.firmware'] libxmlb-0.3.22/data/libxmlb.test.in000066400000000000000000000000761476425255200171160ustar00rootroot00000000000000[Test] Type=session Exec=@installed_test_bindir@/xb-self-test libxmlb-0.3.22/data/meson.build000066400000000000000000000006451476425255200163230ustar00rootroot00000000000000if get_option('tests') and host_machine.system() != 'windows' configure_file( input : 'libxmlb.test.in', output : 'libxmlb.test', configuration : conf, install: true, install_dir: installed_test_datadir, ) install_data([ 'test.desktop', 'test.quirk', 'test.xml', 'test.xml.gz.gz.gz', 'test.xml.xz', 'test.xml.zst', ], install_dir: installed_test_bindir, ) endif libxmlb-0.3.22/data/test.desktop000066400000000000000000000001231476425255200165220ustar00rootroot00000000000000[Desktop Entry] Name=Test Exec=test Icon=application-x-executable Type=Application libxmlb-0.3.22/data/test.quirk000066400000000000000000000000451476425255200162070ustar00rootroot00000000000000[USB\VID_1D50&PID_5119] Plugin = dfu libxmlb-0.3.22/data/test.xml000066400000000000000000000000161476425255200156520ustar00rootroot00000000000000 libxmlb-0.3.22/data/test.xml.gz.gz.gz000066400000000000000000000001711476425255200173310ustar00rootroot00000000000000vv\test.xml.gz.gzSvv\test.xml.gzh.+a`.I-.ѫa|gg'Ok?";W#E1Dd2'hXSlibxmlb-0.3.22/data/test.xml.xz000066400000000000000000000001201476425255200163060ustar00rootroot000000000000007zXZִF!t/Hello world! k-1].V U}YZlibxmlb-0.3.22/data/test.xml.zst000066400000000000000000000000431476425255200164710ustar00rootroot00000000000000(/$Hello world! ^libxmlb-0.3.22/docs/000077500000000000000000000000001476425255200141735ustar00rootroot00000000000000libxmlb-0.3.22/docs/fuzzing.md000066400000000000000000000011141476425255200162060ustar00rootroot00000000000000Fuzzing ======= CC=afl-gcc meson --default-library=static ../ AFL_HARDEN=1 ninja Breaking XMLb ------------- afl-fuzz -m 300 -i fuzzing-src -o findings ./src/xb-tool --force dump @@ afl-fuzz -m 300 -i fuzzing-src -o findings ./src/xb-tool query @@ "component/id" mkdir -p fuzzing-src ./src/xb-tool compile fuzzing-src/appdata.xmlb ../data/fuzzing-src/appdata.xml Breaking XPath -------------- ./src/xb-tool compile xpath.xmlb ../data/fuzzing-src/appdata.xml afl-fuzz -m 300 -i ../data/fuzzing-xpath/ -o findings ./src/xb-tool query-file xpath.xmlb @@ libxmlb-0.3.22/docs/libxmlb-docs.xml000066400000000000000000000036201476425255200172750ustar00rootroot00000000000000 ]> libxmlb Reference Manual About libxmlb libxmlb is a library for querying binary XML. libxmlb Functionality exported by libxmlb for client applications. API Index Index of deprecated API libxmlb-0.3.22/docs/libxmlb.types000066400000000000000000000002741476425255200167150ustar00rootroot00000000000000xb_silo_get_type xb_machine_get_type xb_node_get_type xb_stack_get_type xb_builder_get_type xb_builder_fixup_get_type xb_builder_node_get_type xb_builder_source_get_type xb_query_get_type libxmlb-0.3.22/docs/meson.build000066400000000000000000000002451476425255200163360ustar00rootroot00000000000000gnome.gtkdoc( 'libxmlb', src_dir : [ 'src', join_paths(meson.current_build_dir(), '..', 'src'), ], main_xml : 'libxmlb-docs.xml', install : true ) libxmlb-0.3.22/meson.build000066400000000000000000000117251476425255200154130ustar00rootroot00000000000000project('libxmlb', 'c', version : '0.3.22', license : 'LGPL-2.1-or-later', meson_version : '>=0.60.0', default_options : ['warning_level=2', 'c_std=c99'], ) libxmlb_version = meson.project_version() varr = libxmlb_version.split('.') libxmlb_major_version = varr[0] libxmlb_minor_version = varr[1] libxmlb_micro_version = varr[2] conf = configuration_data() conf.set('XMLB_MAJOR_VERSION', libxmlb_major_version) conf.set('XMLB_MINOR_VERSION', libxmlb_minor_version) conf.set('XMLB_MICRO_VERSION', libxmlb_micro_version) conf.set_quoted('PACKAGE_VERSION', libxmlb_version) # libtool versioning - this applies to libxmlb lt_current = '2' lt_revision = '0' lt_age = '0' lt_version = '@0@.@1@.@2@'.format(lt_current, lt_age, lt_revision) configinc = include_directories('.') # get supported warning flags warning_flags = [ '-Wno-nonnull-compare', '-Wno-aggregate-return', '-Wunused', '-Warray-bounds', '-Wcast-align', '-Wclobbered', '-Wdeclaration-after-statement', '-Wduplicated-branches', '-Wduplicated-cond', '-Wempty-body', '-Wformat=2', '-Wformat-nonliteral', '-Wformat-security', '-Wformat-signedness', '-Wignored-qualifiers', '-Wimplicit-function-declaration', '-Wincompatible-pointer-types-discards-qualifiers', '-Winit-self', '-Wlogical-op', '-Wmissing-declarations', '-Wmissing-format-attribute', '-Wmissing-include-dirs', '-Wmissing-noreturn', '-Wmissing-parameter-type', '-Wmissing-prototypes', '-Wnested-externs', '-Wno-cast-function-type', '-Wno-error=cpp', '-Wno-unknown-pragmas', '-Wno-discarded-qualifiers', '-Wno-missing-field-initializers', '-Wno-strict-aliasing', '-Wno-suggest-attribute=format', '-Wno-unused-parameter', '-Wnull-dereference', '-Wold-style-definition', '-Woverride-init', '-Wpointer-arith', '-Wredundant-decls', '-Wreturn-type', '-Wshadow', '-Wsign-compare', '-Wstrict-aliasing', '-Wstrict-prototypes', '-Wswitch-default', '-Wtype-limits', '-Wundef', '-Wuninitialized', '-Wunused-but-set-variable', '-Wunused-variable', '-Wwrite-strings' ] cc = meson.get_compiler('c') add_project_arguments(cc.get_supported_arguments(warning_flags), language : 'c') if not meson.is_cross_build() add_project_arguments('-fstack-protector-strong', language : 'c') endif if cc.get_id() == 'msvc' error('MSVC is not supported as it does not support __attribute__((cleanup))') endif # enable full RELRO where possible # FIXME: until https://github.com/mesonbuild/meson/issues/1140 is fixed global_link_args = [] release_args = [] test_link_args = [ '-Wl,-z,relro', '-Wl,-z,now', ] if not get_option('debug') release_args += ['-DG_DISABLE_CAST_CHECKS', '-DG_DISABLE_ASSERT'] test_link_args += [ '-Wl,-Bsymbolic', '-fno-plt', ] endif foreach link_arg: test_link_args if cc.has_link_argument(link_arg) global_link_args += link_arg endif endforeach add_project_link_arguments( global_link_args, language: 'c' ) prefix = get_option('prefix') if host_machine.system() == 'windows' bindir = get_option('bindir') installed_test_bindir = get_option('libexecdir') installed_test_datadir = get_option('datadir') else datadir = join_paths(prefix, get_option('datadir')) bindir = join_paths(prefix, get_option('bindir')) libexecdir = join_paths(prefix, get_option('libexecdir')) installed_test_bindir = join_paths(libexecdir, 'installed-tests', meson.project_name()) installed_test_datadir = join_paths(datadir, 'installed-tests', meson.project_name()) endif mandir = join_paths(prefix, get_option('mandir')) gio = dependency('gio-2.0', version : '>= 2.45.8') giounix = dependency('gio-unix-2.0', version : '>= 2.45.8', required: false) lzma = dependency('liblzma', required: get_option('lzma')) if lzma.found() conf.set('HAVE_LZMA', 1) endif zstd = dependency('libzstd', required: get_option('zstd')) if zstd.found() conf.set('HAVE_ZSTD', 1) endif if giounix.found() conf.set('HAVE_GIO_UNIX', '1') endif # Limit our use of GLib API to our minimum version requirement, and what’s # available in Debian Stable. Use of more modern API has to be optional and # protected by GLIB_CHECK_VERSION. add_project_arguments('-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_46', language: 'c') add_project_arguments('-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_58', language: 'c') libxmlb_deps = [ gio, ] if lzma.found() libxmlb_deps += lzma endif if zstd.found() libxmlb_deps += zstd endif # support stemming of search tokens if get_option('stemmer') cc = meson.get_compiler('c') stemmer = cc.find_library('stemmer') libxmlb_deps += stemmer conf.set('HAVE_LIBSTEMMER', 1) endif gnome = import('gnome') conf.set('installed_test_bindir', installed_test_bindir) conf.set_quoted('PACKAGE_NAME', meson.project_name()) conf.set_quoted('VERSION', meson.project_version()) configure_file( output : 'config.h', configuration : conf ) python3 = find_program('python3') subdir('data') subdir('src') if get_option('gtkdoc') gtkdocscan = find_program('gtkdoc-scan', required : true) subdir('docs') endif libxmlb-0.3.22/meson_options.txt000066400000000000000000000012661476425255200167050ustar00rootroot00000000000000option('gtkdoc',type : 'boolean', value : true, description : 'enable developer documentation') option('introspection', type : 'boolean', value : true, description : 'generate GObject Introspection data') option('tests', type : 'boolean', value : true, description : 'enable tests') option('stemmer', type : 'boolean', value : false, description : 'enable stemmer support') option('cli', type : 'boolean', value : true, description : 'build and install the xb-tool CLI') option('lzma', type: 'feature', description : 'enable lzma (xz) support', ) option('zstd', type: 'feature', description: 'enable zstd support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) libxmlb-0.3.22/src/000077500000000000000000000000001476425255200140325ustar00rootroot00000000000000libxmlb-0.3.22/src/gen-def-from-map.py000066400000000000000000000070361476425255200174330ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright 2023 Chun-wei Fan # # SPDX-License-Identifier: LGPL-2.1-or-later # Script to generate .def file from .map files import os import re import sys def get_sym_groups(lines, namespace): sym_groups = [] search_header = True collect_symbols = False search_tail = False # namespace + version of library namespace_with_ver_regex = re.escape(namespace) + r"_[0-9]+\.[0-9]+\.[0-9]+" # Look for symbols added in version in library from map file, # which defines a group of symbols header_regex = re.compile(r"^" + namespace_with_ver_regex) # Look for where to start collecting symbols for the group global_regex = re.compile(r"^\s+global:\s+$") # Look for where to end collecting symbols for the group local_regex = re.compile(r"^\s+local:\s+\*+;\s+$") # Look, if any, since which version were the symbols # group added ending_notail_regex = re.compile(r"^};\s+$") ending_tail_regex = re.compile(r"^}\s+" + namespace_with_ver_regex + ";\s+$") for l in lines: # New symbols group found in map file if search_header and header_regex.match(l): symbols = [] header = l.split()[0] search_header = False # Go through map file to gather up info for this symbols group elif not search_header: if not collect_symbols and global_regex.match(l): collect_symbols = True elif collect_symbols: tail = None # Stop gathering for group if `local: *;` is found if local_regex.match(l): search_tail = True elif search_tail: # Populate symbols group info and append # to the groups we gathered previously if ending_notail_regex.match(l) or ending_tail_regex.match(l): if ending_tail_regex.match(l): tail_ = l.split()[1] tail = tail_[: tail_.index(";")] sym_group = { "header": header, "symbols": symbols, "tail": tail, } sym_groups.append(sym_group) search_tail = False collect_symbols = False search_header = True # Gather up symbols for this group else: symbol = l[: l.index(";")].strip() symbols.append(symbol) return sym_groups def output_def_file(def_file, sym_groups): with open(def_file, "w") as out: out.write("EXPORTS\n") for group in sym_groups: out.write("\n") if group["tail"] is not None: out.write( "; APIs added in %s since %s\n" % (group["header"], group["tail"]) ) else: out.write("; APIs added in %s\n" % group["header"]) for symbol in group["symbols"]: out.write("%s\n" % symbol) if __name__ == "__main__": if len(sys.argv) != 4: raise SystemExit("%s " % sys.argv[0]) mapfile = sys.argv[1] namespace = sys.argv[2] def_file = sys.argv[3] with open(mapfile, "r") as m: lines = m.readlines() groups = get_sym_groups(lines, namespace) output_def_file(def_file, groups) libxmlb-0.3.22/src/generate-version-script.py000066400000000000000000000110041476425255200211570ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright 2017 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1-or-later import sys import argparse import xml.etree.ElementTree as ET XMLNS = "{http://www.gtk.org/introspection/core/1.0}" XMLNS_C = "{http://www.gtk.org/introspection/c/1.0}" def parse_version(ver): return tuple(map(int, ver.split("."))) def usage(return_code): """print usage and exit with the supplied return code""" if return_code == 0: out = sys.stdout else: out = sys.stderr out.write("usage: %s \n" % sys.argv[0]) sys.exit(return_code) class LdVersionScript: """Rasterize some text""" def __init__(self, library_name): self.library_name = library_name self.releases = {} self.overrides = {} def _add_node(self, node): identifier = node.attrib[XMLNS_C + "identifier"] introspectable = int(node.get("introspectable", 1)) version = node.get("version", None) if introspectable and not version: print("No version for", identifier) sys.exit(1) if not version: return None version = node.attrib["version"] if version not in self.releases: self.releases[version] = [] release = self.releases[version] if identifier not in release: release.append(identifier) return version def _add_cls(self, cls): # add all class functions for node in cls.findall(XMLNS + "function"): self._add_node(node) # choose the lowest version method for the _get_type symbol version_lowest = None # add all class methods for node in cls.findall(XMLNS + "method"): version_tmp = self._add_node(node) if version_tmp: if not version_lowest or parse_version(version_tmp) < parse_version( version_lowest ): version_lowest = version_tmp # add the constructor for node in cls.findall(XMLNS + "constructor"): version_tmp = self._add_node(node) if version_tmp: if not version_lowest or parse_version(version_tmp) < parse_version( version_lowest ): version_lowest = version_tmp if "{http://www.gtk.org/introspection/glib/1.0}get-type" not in cls.attrib: return type_name = cls.attrib["{http://www.gtk.org/introspection/glib/1.0}get-type"] # finally add the get_type symbol version = self.overrides.get(type_name, version_lowest) if version: self.releases[version].append(type_name) def import_gir(self, filename): tree = ET.parse(filename) root = tree.getroot() for ns in root.findall(XMLNS + "namespace"): for node in ns.findall(XMLNS + "function"): self._add_node(node) for cls in ns.findall(XMLNS + "record"): self._add_cls(cls) for cls in ns.findall(XMLNS + "class"): self._add_cls(cls) def render(self): # get a sorted list of all the versions versions = [] for version in self.releases: versions.append(version) # output the version data to a file verout = "# generated automatically, do not edit!\n" oldversion = None for version in sorted(versions, key=parse_version): symbols = sorted(self.releases[version]) verout += "\n%s_%s {\n" % (self.library_name, version) verout += " global:\n" for symbol in symbols: verout += " %s;\n" % symbol verout += " local: *;\n" if oldversion: verout += "} %s_%s;\n" % (self.library_name, oldversion) else: verout += "};\n" oldversion = version return verout if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "-r", "--override", action="append", nargs=2, metavar=("symbol", "version") ) args, argv = parser.parse_known_args() if len(argv) != 3: usage(1) ld = LdVersionScript(library_name=argv[0]) if args.override: for override_symbol, override_version in args.override: ld.overrides[override_symbol] = override_version ld.import_gir(argv[1]) open(argv[2], "w", newline="\n").write(ld.render()) libxmlb-0.3.22/src/libxmlb.map000066400000000000000000000144331476425255200161670ustar00rootroot00000000000000# generated automatically, do not edit! LIBXMLB_0.1.0 { global: xb_builder_add_locale; xb_builder_append_guid; xb_builder_compile; xb_builder_ensure; xb_builder_get_type; xb_builder_import_node; xb_builder_import_source; xb_builder_new; xb_builder_node_add_child; xb_builder_node_add_flag; xb_builder_node_get_attr; xb_builder_node_get_children; xb_builder_node_get_element; xb_builder_node_get_text; xb_builder_node_get_type; xb_builder_node_has_flag; xb_builder_node_insert; xb_builder_node_insert_text; xb_builder_node_new; xb_builder_node_remove_attr; xb_builder_node_set_attr; xb_builder_node_set_element; xb_builder_node_set_text; xb_builder_source_get_type; xb_builder_source_set_info; xb_builder_source_set_prefix; xb_node_export; xb_node_get_attr; xb_node_get_attr_as_uint; xb_node_get_child; xb_node_get_children; xb_node_get_data; xb_node_get_depth; xb_node_get_element; xb_node_get_next; xb_node_get_parent; xb_node_get_root; xb_node_get_text; xb_node_get_text_as_uint; xb_node_get_type; xb_node_query; xb_node_query_attr; xb_node_query_attr_as_uint; xb_node_query_export; xb_node_query_first; xb_node_query_text; xb_node_query_text_as_uint; xb_node_set_data; xb_silo_export; xb_silo_get_bytes; xb_silo_get_guid; xb_silo_get_root; xb_silo_get_size; xb_silo_get_type; xb_silo_is_valid; xb_silo_load_from_bytes; xb_silo_load_from_file; xb_silo_new; xb_silo_new_from_xml; xb_silo_query; xb_silo_query_first; xb_silo_save_to_file; xb_silo_to_string; xb_silo_watch_file; local: *; }; LIBXMLB_0.1.1 { global: xb_builder_node_depth; xb_builder_node_get_child; xb_builder_node_get_parent; xb_builder_node_remove_child; xb_builder_node_traverse; xb_builder_node_unlink; xb_builder_set_profile_flags; xb_builder_source_load_file; xb_builder_source_load_xml; xb_builder_source_new; xb_machine_add_method; xb_machine_add_opcode_fixup; xb_machine_add_operator; xb_machine_add_text_handler; xb_machine_get_type; xb_machine_new; xb_machine_parse; xb_machine_run; xb_machine_set_debug_flags; xb_opcode_cmp_str; xb_opcode_cmp_val; xb_opcode_get_kind; xb_opcode_get_str; xb_opcode_get_val; xb_opcode_kind_from_string; xb_opcode_kind_to_string; xb_silo_get_profile_string; xb_silo_invalidate; xb_silo_set_profile_flags; local: *; } LIBXMLB_0.1.0; LIBXMLB_0.1.2 { global: xb_builder_source_load_bytes; xb_silo_export_file; xb_string_append_union; xb_string_escape; local: *; } LIBXMLB_0.1.1; LIBXMLB_0.1.3 { global: xb_builder_add_fixup; xb_builder_fixup_get_max_depth; xb_builder_fixup_get_type; xb_builder_fixup_new; xb_builder_fixup_set_max_depth; xb_builder_node_get_attr_as_uint; xb_builder_node_get_text_as_uint; xb_builder_node_sort_children; xb_builder_source_add_fixup; xb_machine_get_stack_size; xb_machine_set_stack_size; local: *; } LIBXMLB_0.1.2; LIBXMLB_0.1.4 { global: xb_machine_parse_full; xb_node_query_full; xb_opcode_to_string; xb_query_bind_str; xb_query_bind_val; xb_query_get_limit; xb_query_get_type; xb_query_get_xpath; xb_query_new; xb_query_set_limit; xb_silo_query_build_index; xb_stack_get_type; xb_stack_to_string; local: *; } LIBXMLB_0.1.3; LIBXMLB_0.1.5 { global: xb_builder_node_export; local: *; } LIBXMLB_0.1.4; LIBXMLB_0.1.6 { global: xb_query_new_full; local: *; } LIBXMLB_0.1.5; LIBXMLB_0.1.7 { global: xb_builder_source_add_adapter; xb_builder_source_ctx_get_bytes; xb_builder_source_ctx_get_filename; xb_builder_source_ctx_get_stream; xb_builder_source_ctx_get_type; local: *; } LIBXMLB_0.1.6; LIBXMLB_0.1.11 { global: xb_node_query_first_full; local: *; } LIBXMLB_0.1.7; LIBXMLB_0.1.12 { global: xb_builder_node_get_first_child; xb_builder_node_get_last_child; xb_builder_node_get_tail; xb_builder_node_set_tail; xb_node_get_tail; xb_node_transmogrify; local: *; } LIBXMLB_0.1.11; LIBXMLB_0.1.13 { global: xb_silo_query_first_full; xb_silo_query_full; local: *; } LIBXMLB_0.1.12; LIBXMLB_0.1.15 { global: xb_builder_source_add_simple_adapter; xb_query_get_flags; xb_query_set_flags; local: *; } LIBXMLB_0.1.13; LIBXMLB_0.2.0 { global: xb_machine_opcode_func_init; xb_machine_stack_pop; xb_machine_stack_push; xb_machine_stack_push_integer; xb_machine_stack_push_text; xb_machine_stack_push_text_static; xb_machine_stack_push_text_steal; xb_node_get_silo; xb_opcode_func_init; xb_opcode_integer_init; xb_opcode_text_init; xb_opcode_text_init_static; xb_opcode_text_init_steal; xb_silo_get_enable_node_cache; xb_silo_set_enable_node_cache; xb_stack_pop; xb_stack_push; local: *; } LIBXMLB_0.1.15; LIBXMLB_0.3.0 { global: xb_machine_run_with_bindings; xb_node_query_first_with_context; xb_node_query_with_context; xb_query_context_clear; xb_query_context_copy; xb_query_context_free; xb_query_context_get_bindings; xb_query_context_get_flags; xb_query_context_get_limit; xb_query_context_get_type; xb_query_context_init; xb_query_context_set_flags; xb_query_context_set_limit; xb_silo_lookup_query; xb_silo_query_first_with_context; xb_silo_query_with_context; xb_value_bindings_bind_str; xb_value_bindings_bind_val; xb_value_bindings_clear; xb_value_bindings_copy; xb_value_bindings_copy_binding; xb_value_bindings_free; xb_value_bindings_get_type; xb_value_bindings_init; xb_value_bindings_is_bound; xb_value_bindings_lookup_opcode; local: *; } LIBXMLB_0.2.0; LIBXMLB_0.3.1 { global: xb_builder_node_add_token; xb_builder_node_get_tokens; xb_builder_node_tokenize_text; local: *; } LIBXMLB_0.3.0; LIBXMLB_0.3.4 { global: xb_node_attr_iter_init; xb_node_attr_iter_next; xb_node_child_iter_init; xb_node_child_iter_loop; xb_node_child_iter_next; local: *; } LIBXMLB_0.3.1; LIBXMLB_0.3.19 { global: xb_version_string; local: *; } LIBXMLB_0.3.4; libxmlb-0.3.22/src/libxmlb/000077500000000000000000000000001476425255200154635ustar00rootroot00000000000000libxmlb-0.3.22/src/libxmlb/meson.build000066400000000000000000000004771476425255200176350ustar00rootroot00000000000000# FIXME: We need to copy the headers to ${build_root}/libxmlb # in order for including the headers from parent projects to work # until https://github.com/mesonbuild/meson/issues/2546 is fixed foreach header : xb_headers configure_file( copy : true, input : header, output : '@PLAINNAME@', ) endforeach libxmlb-0.3.22/src/meson.build000066400000000000000000000143421476425255200162000ustar00rootroot00000000000000libxmlb_version_h = configure_file( input : 'xb-version.h.in', output : 'xb-version.h', configuration : conf ) install_headers( 'xmlb.h', subdir : 'libxmlb-2', ) xb_headers = files( 'xb-builder.h', 'xb-builder-fixup.h', 'xb-builder-node.h', 'xb-builder-source.h', 'xb-builder-source-ctx.h', 'xb-compile.h', 'xb-machine.h', 'xb-node.h', 'xb-node-query.h', 'xb-node-silo.h', 'xb-opcode.h', 'xb-query.h', 'xb-query-context.h', 'xb-silo-export.h', 'xb-silo.h', 'xb-silo-query.h', 'xb-stack.h', 'xb-string.h', 'xb-value-bindings.h', ) + [libxmlb_version_h] install_headers( xb_headers, subdir : 'libxmlb-2/libxmlb', ) subdir('libxmlb') mapfile = 'libxmlb.map' vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile) extra_sources = [] if zstd.found() extra_sources += ['xb-zstd-decompressor.c'] endif if lzma.found() extra_sources += ['xb-lzma-decompressor.c'] endif def_file_target = custom_target( 'libxmlb-defile', input: mapfile, output: 'libxmlb.def', command: [ python3, join_paths(meson.current_source_dir(), 'gen-def-from-map.py'), '@INPUT@', 'LIBXMLB', '@OUTPUT@', ], ) libxmlb = library( 'xmlb', sources : [ 'xb-builder.c', 'xb-builder-fixup.c', 'xb-builder-node.c', 'xb-builder-source.c', 'xb-builder-source-ctx.c', 'xb-common.c', 'xb-machine.c', 'xb-opcode.c', 'xb-node.c', 'xb-node-query.c', 'xb-query.c', 'xb-query-context.c', 'xb-silo.c', 'xb-silo-export.c', 'xb-silo-node.c', 'xb-silo-query.c', 'xb-stack.c', 'xb-string.c', 'xb-value-bindings.c', 'xb-version.c', ] + extra_sources, soversion : lt_current, version : lt_version, include_directories : [ configinc, ], dependencies : libxmlb_deps, c_args : release_args, link_args : cc.get_supported_link_arguments([vflag]), vs_module_defs: def_file_target, link_depends : mapfile, install : true ) libxmlb_dep = declare_dependency( link_with : libxmlb, include_directories : include_directories('.'), dependencies : libxmlb_deps, variables : { 'lzma': '@0@'.format(lzma.found()), 'zstd': '@0@'.format(zstd.found()), }, ) if get_option('cli') xb_tool = executable( 'xb-tool', sources : [ 'xb-tool.c', ], include_directories : [ configinc, ], dependencies : [ gio, ], link_with : [ libxmlb, ], install : true, install_dir : bindir ) configure_file( input : 'xb-tool.1', output : 'xb-tool.1', configuration : conf, install: true, install_dir: join_paths(mandir, 'man1'), ) endif pkgg = import('pkgconfig') pkgg.generate(libxmlb, requires : [ 'gio-2.0' ], subdirs : 'libxmlb-2', version : meson.project_version(), name : 'libxmlb', filebase : 'xmlb', description : 'libxmlb is a library to create or query compressed XML files', variables : { 'lzma': '@0@'.format(lzma.found()), 'zstd': '@0@'.format(zstd.found()), }, ) if get_option('introspection') gir = gnome.generate_gir(libxmlb, sources : [ 'xb-builder.c', 'xb-builder.h', 'xb-builder-fixup.c', 'xb-builder-fixup.h', 'xb-builder-node.c', 'xb-builder-node.h', 'xb-builder-source.c', 'xb-builder-source.h', 'xb-builder-source-ctx.c', 'xb-builder-source-ctx.h', 'xb-common.c', 'xb-machine.c', 'xb-machine.h', 'xb-node.c', 'xb-node.h', 'xb-node-query.c', 'xb-node-query.h', 'xb-node-silo.h', 'xb-opcode.c', 'xb-opcode.h', 'xb-query.c', 'xb-query.h', 'xb-query-context.c', 'xb-query-context.h', 'xb-silo.c', 'xb-silo.h', 'xb-silo-export.c', 'xb-silo-export.h', 'xb-silo-query.c', 'xb-silo-query.h', 'xb-stack.c', 'xb-stack.h', 'xb-string.c', 'xb-string.h', 'xb-value-bindings.c', 'xb-value-bindings.h', 'xb-version.c', libxmlb_version_h, ], nsversion : '2.0', namespace : 'Xmlb', symbol_prefix : [ 'xb', 'xmlb', 'libxmlb', ], identifier_prefix : 'Xb', export_packages : 'xmlb', header : 'xmlb.h', dependencies : [ gio, ], includes : [ 'Gio-2.0', 'GObject-2.0', ], link_with : [ libxmlb, ], install : true ) # Verify the map file is correct -- note we can't actually use the generated # file for two reasons: # # 1. We don't hard depend on GObject Introspection # 2. The map file is required to build the lib that the GIR is built from # # To avoid the circular dep, and to ensure we don't change exported API # accidentally actually check in a version of the version script to git. mapfile_target = custom_target('libxmlb-mapfile', input: gir[0], output: 'libxmlb.map', command: [ python3, join_paths(meson.current_source_dir(), 'generate-version-script.py'), 'LIBXMLB', '@INPUT@', '@OUTPUT@', ], ) diffcmd = find_program('diff') test('libxmlb-exported-api', diffcmd, args : [ '-urNp', join_paths(meson.current_source_dir(), 'libxmlb.map'), mapfile_target, ], ) endif if get_option('tests') testdatadirs = environment() testdatadirs.set('G_TEST_SRCDIR', join_paths(meson.project_source_root(), 'data')) testdatadirs.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'xb-self-test', sources : [ 'xb-builder.c', 'xb-builder-fixup.c', 'xb-builder-fixup.c', 'xb-builder-node.c', 'xb-builder-source.c', 'xb-builder-source-ctx.c', 'xb-common.c', 'xb-machine.c', 'xb-node.c', 'xb-node-query.c', 'xb-opcode.c', 'xb-self-test.c', 'xb-query.c', 'xb-query-context.c', 'xb-silo.c', 'xb-silo-export.c', 'xb-silo-node.c', 'xb-silo-query.c', 'xb-stack.c', 'xb-string.c', 'xb-value-bindings.c', ] + extra_sources, include_directories : [ configinc, ], dependencies : [ gio, libxmlb_dep, ], c_args: [ '-DSRCDIR="' + join_paths(meson.project_source_root(), 'data') + '"', ], install : true, install_dir : installed_test_bindir ) test('xb-self-test', e, env : testdatadirs) endif libxmlb-0.3.22/src/xb-builder-fixup-private.h000066400000000000000000000007101476425255200210370ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-builder-fixup.h" G_BEGIN_DECLS gboolean xb_builder_fixup_node(XbBuilderFixup *self, XbBuilderNode *bn, GError **error) G_GNUC_NON_NULL(1, 2); const gchar * xb_builder_fixup_get_id(XbBuilderFixup *self) G_GNUC_NON_NULL(1); gchar * xb_builder_fixup_get_guid(XbBuilderFixup *self) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-builder-fixup.c000066400000000000000000000113731476425255200173710ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbSilo" #include "config.h" #include #include "xb-builder-fixup-private.h" typedef struct { gchar *id; XbBuilderFixupFunc func; gpointer user_data; GDestroyNotify user_data_free; gint max_depth; } XbBuilderFixupPrivate; G_DEFINE_TYPE_WITH_PRIVATE(XbBuilderFixup, xb_builder_fixup, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_builder_fixup_get_instance_private(o)) typedef struct { XbBuilderFixup *self; gboolean ret; GError *error; } XbBuilderFixupHelper; static gboolean xb_builder_fixup_cb(XbBuilderNode *bn, gpointer data) { XbBuilderFixupHelper *helper = (XbBuilderFixupHelper *)data; XbBuilderFixup *self = XB_BUILDER_FIXUP(helper->self); XbBuilderFixupPrivate *priv = GET_PRIVATE(self); /* run all node funcs on the source */ if (!priv->func(self, bn, priv->user_data, &helper->error)) { helper->ret = FALSE; return TRUE; } /* keep going */ return FALSE; } /* private */ gboolean xb_builder_fixup_node(XbBuilderFixup *self, XbBuilderNode *bn, GError **error) { XbBuilderFixupPrivate *priv = GET_PRIVATE(self); XbBuilderFixupHelper helper = { .self = self, .ret = TRUE, .error = NULL, }; /* visit each node */ xb_builder_node_traverse(bn, G_PRE_ORDER, G_TRAVERSE_ALL, priv->max_depth, xb_builder_fixup_cb, &helper); if (!helper.ret) { g_propagate_error(error, helper.error); return FALSE; } return TRUE; } /** * xb_builder_fixup_get_id: * @self: a #XbBuilderFixup * * Gets the fixup ID. * * Returns: string, e.g. `AppStreamUpgrade` * * Since: 0.1.3 **/ const gchar * xb_builder_fixup_get_id(XbBuilderFixup *self) { XbBuilderFixupPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_FIXUP(self), NULL); return priv->id; } /* private */ gchar * xb_builder_fixup_get_guid(XbBuilderFixup *self) { g_autoptr(GString) str = g_string_new("func-id="); XbBuilderFixupPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_FIXUP(self), NULL); /* build GUID using ID and max-depth, if set */ g_string_append(str, priv->id); if (priv->max_depth != -1) g_string_append_printf(str, "@%i", priv->max_depth); return g_string_free(g_steal_pointer(&str), FALSE); } /** * xb_builder_fixup_get_max_depth: * @self: a #XbBuilderFixup * * Gets the maximum depth used for this fixup, if each node is being visited. * * Returns: integer, or -1 if unset * * Since: 0.1.3 **/ gint xb_builder_fixup_get_max_depth(XbBuilderFixup *self) { XbBuilderFixupPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_FIXUP(self), 0); return priv->max_depth; } /** * xb_builder_fixup_set_max_depth: * @self: a #XbBuilderFixup * @max_depth: integer, -1 for "all" * * Sets the maximum depth used for this fixup. Use a @max_depth of 0 to only * visit the root node. * * Setting a maximum depth may increase performance considerably if using * fixup functions on large and deeply nested XML files. * * Since: 0.1.3 **/ void xb_builder_fixup_set_max_depth(XbBuilderFixup *self, gint max_depth) { XbBuilderFixupPrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_FIXUP(self)); priv->max_depth = max_depth; } static void xb_builder_fixup_finalize(GObject *obj) { XbBuilderFixup *self = XB_BUILDER_FIXUP(obj); XbBuilderFixupPrivate *priv = GET_PRIVATE(self); if (priv->user_data_free != NULL) priv->user_data_free(priv->user_data); g_free(priv->id); G_OBJECT_CLASS(xb_builder_fixup_parent_class)->finalize(obj); } static void xb_builder_fixup_class_init(XbBuilderFixupClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = xb_builder_fixup_finalize; } static void xb_builder_fixup_init(XbBuilderFixup *self) { XbBuilderFixupPrivate *priv = GET_PRIVATE(self); priv->max_depth = -1; } /** * xb_builder_fixup_new: * @id: a text ID value, e.g. `AppStreamUpgrade` * @func: a callback * @user_data: user pointer to pass to @func, or %NULL * @user_data_free: a function which gets called to free @user_data, or %NULL * * Creates a function that will get run on every #XbBuilderNode compile creates. * * Returns: a new #XbBuilderFixup * * Since: 0.1.3 **/ XbBuilderFixup * xb_builder_fixup_new(const gchar *id, XbBuilderFixupFunc func, gpointer user_data, GDestroyNotify user_data_free) { XbBuilderFixup *self = g_object_new(XB_TYPE_BUILDER_FIXUP, NULL); XbBuilderFixupPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_FIXUP(self), NULL); g_return_val_if_fail(id != NULL, NULL); g_return_val_if_fail(func != NULL, NULL); priv->id = g_strdup(id); priv->func = func; priv->user_data = user_data; priv->user_data_free = user_data_free; return self; } libxmlb-0.3.22/src/xb-builder-fixup.h000066400000000000000000000021401476425255200173660ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-builder-node.h" G_BEGIN_DECLS #define XB_TYPE_BUILDER_FIXUP (xb_builder_fixup_get_type()) G_DECLARE_DERIVABLE_TYPE(XbBuilderFixup, xb_builder_fixup, XB, BUILDER_FIXUP, GObject) struct _XbBuilderFixupClass { GObjectClass parent_class; /*< private >*/ void (*_xb_reserved1)(void); void (*_xb_reserved2)(void); void (*_xb_reserved3)(void); void (*_xb_reserved4)(void); void (*_xb_reserved5)(void); void (*_xb_reserved6)(void); void (*_xb_reserved7)(void); }; typedef gboolean (*XbBuilderFixupFunc)(XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error); XbBuilderFixup * xb_builder_fixup_new(const gchar *id, XbBuilderFixupFunc func, gpointer user_data, GDestroyNotify user_data_free) G_GNUC_NON_NULL(1, 2); gint xb_builder_fixup_get_max_depth(XbBuilderFixup *self) G_GNUC_NON_NULL(1); void xb_builder_fixup_set_max_depth(XbBuilderFixup *self, gint max_depth) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-builder-node-private.h000066400000000000000000000027441476425255200206420ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-builder-node.h" #include "xb-compile.h" G_BEGIN_DECLS typedef struct { /*< private >*/ gchar *name; guint32 name_idx; gchar *value; guint32 value_idx; } XbBuilderNodeAttr; GPtrArray * xb_builder_node_get_attrs(XbBuilderNode *self) G_GNUC_NON_NULL(1); guint32 xb_builder_node_size(XbBuilderNode *self) G_GNUC_NON_NULL(1); guint32 xb_builder_node_get_offset(XbBuilderNode *self) G_GNUC_NON_NULL(1); void xb_builder_node_set_offset(XbBuilderNode *self, guint32 offset) G_GNUC_NON_NULL(1); gint xb_builder_node_get_priority(XbBuilderNode *self) G_GNUC_NON_NULL(1); void xb_builder_node_set_priority(XbBuilderNode *self, gint priority) G_GNUC_NON_NULL(1); guint32 xb_builder_node_get_element_idx(XbBuilderNode *self) G_GNUC_NON_NULL(1); void xb_builder_node_set_element_idx(XbBuilderNode *self, guint32 element_idx) G_GNUC_NON_NULL(1); guint32 xb_builder_node_get_text_idx(XbBuilderNode *self) G_GNUC_NON_NULL(1); void xb_builder_node_set_text_idx(XbBuilderNode *self, guint32 text_idx) G_GNUC_NON_NULL(1); guint32 xb_builder_node_get_tail_idx(XbBuilderNode *self) G_GNUC_NON_NULL(1); void xb_builder_node_set_tail_idx(XbBuilderNode *self, guint32 tail_idx) G_GNUC_NON_NULL(1); void xb_builder_node_add_token_idx(XbBuilderNode *self, guint32 tail_idx) G_GNUC_NON_NULL(1); GArray * xb_builder_node_get_token_idxs(XbBuilderNode *self) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-builder-node.c000066400000000000000000000764621476425255200171750ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbSilo" #include "config.h" #include #include #include "xb-builder-node-private.h" #include "xb-opcode-private.h" #include "xb-silo-private.h" #include "xb-string-private.h" typedef struct { guint32 offset; gint priority; XbBuilderNodeFlags flags; gchar *element; guint32 element_idx; gchar *text; guint32 text_idx; gchar *tail; guint32 tail_idx; XbBuilderNode *parent; /* noref */ /* Around 87% of all XML nodes have zero children, so this array is only * allocated if it’s non-empty. %NULL means an empty array. */ GPtrArray *children; /* (element-type XbBuilderNode) (nullable) */ /* Around 80% of all XML nodes have zero attributes, so this array is only * allocated if it’s non-empty. %NULL means an empty array. */ GPtrArray *attrs; /* (element-type XbBuilderNodeAttr) (nullable) */ /* Most nodes will have no tokens */ GPtrArray *tokens; /* (element-type utf8) (nullable) */ GArray *token_idxs; /* (element-type guint32) (nullable) */ } XbBuilderNodePrivate; G_DEFINE_TYPE_WITH_PRIVATE(XbBuilderNode, xb_builder_node, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_builder_node_get_instance_private(o)) static void xb_builder_node_attr_free(XbBuilderNodeAttr *attr); /** * xb_builder_node_has_flag: * @self: a #XbBuilderNode * @flag: a #XbBuilderNodeFlags * * Checks a flag on the builder node. * * Returns: %TRUE if @flag is set * * Since: 0.1.0 **/ gboolean xb_builder_node_has_flag(XbBuilderNode *self, XbBuilderNodeFlags flag) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), FALSE); return (priv->flags & flag) > 0; } /** * xb_builder_node_add_flag: * @self: a #XbBuilderNode * @flag: a #XbBuilderNodeFlags * * Adds a flag to the builder node. * * Since: 0.1.0 **/ void xb_builder_node_add_flag(XbBuilderNode *self, XbBuilderNodeFlags flag) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_NODE(self)); if ((priv->flags & flag) != 0) return; /* do in-place */ if ((flag & XB_BUILDER_NODE_FLAG_STRIP_TEXT) > 0 && priv->text != NULL) g_strstrip(priv->text); priv->flags |= flag; for (guint i = 0; priv->children != NULL && i < priv->children->len; i++) { XbBuilderNode *c = g_ptr_array_index(priv->children, i); xb_builder_node_add_flag(c, flag); } } /** * xb_builder_node_get_element: * @self: a #XbBuilderNode * * Gets the element from the builder node. * * Returns: string, or %NULL if unset * * Since: 0.1.0 **/ const gchar * xb_builder_node_get_element(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL); return priv->element; } /** * xb_builder_node_set_element: * @self: a #XbBuilderNode * @element: a string element * * Sets the element name on the builder node. * * Since: 0.1.0 **/ void xb_builder_node_set_element(XbBuilderNode *self, const gchar *element) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_NODE(self)); g_free(priv->element); priv->element = g_strdup(element); } /** * xb_builder_node_get_attr: * @self: a #XbBuilderNode * @name: attribute name, e.g. `type` * * Gets an attribute from the builder node. * * Returns: string, or %NULL if unset * * Since: 0.1.0 **/ const gchar * xb_builder_node_get_attr(XbBuilderNode *self, const gchar *name) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL); g_return_val_if_fail(name != NULL, NULL); if (priv->attrs == NULL) return NULL; for (guint i = 0; i < priv->attrs->len; i++) { XbBuilderNodeAttr *a = g_ptr_array_index(priv->attrs, i); if (g_strcmp0(a->name, name) == 0) return a->value; } return NULL; } /** * xb_builder_node_get_attr_as_uint: * @self: a #XbBuilderNode * @name: attribute name, e.g. `priority` * * Gets an attribute from the builder node. * * Returns: integer, or 0 if unset * * Since: 0.1.3 **/ guint64 xb_builder_node_get_attr_as_uint(XbBuilderNode *self, const gchar *name) { const gchar *tmp = xb_builder_node_get_attr(self, name); if (tmp == NULL) return 0; if (g_str_has_prefix(tmp, "0x")) return g_ascii_strtoull(tmp + 2, NULL, 16); return g_ascii_strtoll(tmp, NULL, 10); } /** * xb_builder_node_get_text: * @self: a #XbBuilderNode * * Gets the text from the builder node. * * Returns: string, or %NULL if unset * * Since: 0.1.0 **/ const gchar * xb_builder_node_get_text(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL); return priv->text; } /** * xb_builder_node_get_text_as_uint: * @self: a #XbBuilderNode * * Gets the text from the builder node. * * Returns: integer, or 0 if unset * * Since: 0.1.3 **/ guint64 xb_builder_node_get_text_as_uint(XbBuilderNode *self) { const gchar *tmp = xb_builder_node_get_text(self); if (tmp == NULL) return 0; if (g_str_has_prefix(tmp, "0x")) return g_ascii_strtoull(tmp + 2, NULL, 16); return g_ascii_strtoll(tmp, NULL, 10); } /** * xb_builder_node_get_tail: * @self: a #XbBuilderNode * * Gets the tail from the builder node. * * Returns: string, or %NULL if unset * * Since: 0.1.12 **/ const gchar * xb_builder_node_get_tail(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL); return priv->tail; } /* private */ /* Returns NULL if the array is empty */ GPtrArray * xb_builder_node_get_attrs(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL); return priv->attrs; } static gchar * xb_builder_node_parse_literal_text(XbBuilderNode *self, const gchar *text, gssize text_len) { GString *tmp; guint newline_count = 0; g_auto(GStrv) split = NULL; gsize text_len_safe; /* sanity check */ if (text == NULL) return NULL; /* we know this has been pre-fixed */ text_len_safe = text_len >= 0 ? (gsize)text_len : strlen(text); if (xb_builder_node_has_flag(self, XB_BUILDER_NODE_FLAG_LITERAL_TEXT)) return g_strndup(text, text_len_safe); /* all whitespace? */ if (xb_string_isspace(text, text_len_safe)) return NULL; /* all on one line, no trailing or leading whitespace */ if (g_strstr_len(text, text_len, "\n") == NULL) return g_strndup(text, text_len_safe); /* split the text into lines */ tmp = g_string_sized_new((gsize)text_len_safe + 1); split = g_strsplit(text, "\n", -1); for (guint i = 0; split[i] != NULL; i++) { /* if this is a blank line we end the paragraph mode * and swallow the newline. If we see exactly two * newlines in sequence then do a paragraph break */ if (split[i][0] == '\0') { newline_count++; continue; } /* if the line just before this one was not a newline * then separate the words with a space */ if (newline_count == 1 && tmp->len > 0) g_string_append(tmp, " "); /* if we had more than one newline in sequence add a paragraph * break */ if (newline_count > 1) g_string_append(tmp, "\n\n"); /* add the actual stripped text */ g_string_append(tmp, split[i]); /* this last section was paragraph */ newline_count = 1; } /* success */ return g_string_free(tmp, FALSE); } /** * xb_builder_node_tokenize_text: * @self: a #XbBuilderNode * * Tokenize text added with xb_builder_node_set_text(). * * When searching, libxmlb often has to tokenize strings before they can be * compared. This is done in the "fast path" and makes searching for non-ASCII * text much slower. * * Adding the tokens to the deduplicated string table allows much faster * searching at the expense of a ~5% size increase of the silo. * * This function adds all valid UTF-8 and ASCII search words generated from * the value of xb_builder_node_set_text(). * * The transliteration locale (e.g. `en_GB`) is read from the `xml:lang` * node attribute if set. * * Since: 0.3.1 **/ void xb_builder_node_tokenize_text(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); const gchar *xml_lang = xb_builder_node_get_attr(self, "xml:lang"); guint ascii_tokens_sz; guint tokens_sz; g_autofree gchar **ascii_tokens = NULL; g_autofree gchar **tokens = NULL; g_return_if_fail(XB_IS_BUILDER_NODE(self)); if (priv->text == NULL) return; tokens = g_str_tokenize_and_fold(priv->text, xml_lang, &ascii_tokens); /* preallocate the right array size (and more for invalid tokens) */ tokens_sz = g_strv_length(tokens); ascii_tokens_sz = g_strv_length(ascii_tokens); if (priv->tokens == NULL) priv->tokens = g_ptr_array_new_full(tokens_sz + ascii_tokens_sz, g_free); /* add all valid UTF-8 and ASCII tokens */ for (guint i = 0; i < tokens_sz; i++) { if (!xb_string_token_valid(tokens[i])) { g_free(g_steal_pointer(&tokens[i])); continue; } g_ptr_array_add(priv->tokens, g_steal_pointer(&tokens[i])); } for (guint i = 0; i < ascii_tokens_sz; i++) { if (!xb_string_token_valid(ascii_tokens[i])) { g_free(g_steal_pointer(&ascii_tokens[i])); continue; } g_ptr_array_add(priv->tokens, g_steal_pointer(&ascii_tokens[i])); } /* add this so we can set XbSiloNodeFlag.TOKENIZE_TEXT */ xb_builder_node_add_flag(self, XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT); } /** * xb_builder_node_set_text: * @self: a #XbBuilderNode * @text: (allow-none): a string * @text_len: length of @text, or -1 if @text is NUL terminated * * Sets the text on the builder node. * * Since: 0.1.0 **/ void xb_builder_node_set_text(XbBuilderNode *self, const gchar *text, gssize text_len) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_NODE(self)); /* old data */ g_free(priv->text); priv->text = xb_builder_node_parse_literal_text(self, text, text_len); priv->flags |= XB_BUILDER_NODE_FLAG_HAS_TEXT; /* strip before tokenization */ if ((priv->flags & XB_BUILDER_NODE_FLAG_STRIP_TEXT) > 0 && priv->text != NULL) g_strstrip(priv->text); /* tokenize */ if (priv->flags & XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT) xb_builder_node_tokenize_text(self); } /** * xb_builder_node_set_tail: * @self: a #XbBuilderNode * @tail: (allow-none): a string * @tail_len: length of @tail, or -1 if @tail is NUL terminated * * Sets the tail on the builder node. * * Since: 0.1.12 **/ void xb_builder_node_set_tail(XbBuilderNode *self, const gchar *tail, gssize tail_len) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_NODE(self)); /* old data */ g_free(priv->tail); priv->tail = xb_builder_node_parse_literal_text(self, tail, tail_len); priv->flags |= XB_BUILDER_NODE_FLAG_HAS_TAIL; } /** * xb_builder_node_set_attr: * @self: a #XbBuilderNode * @name: attribute name, e.g. `type` * @value: attribute value, e.g. `desktop` * * Adds an attribute to the builder node. * * Since: 0.1.0 **/ void xb_builder_node_set_attr(XbBuilderNode *self, const gchar *name, const gchar *value) { XbBuilderNodeAttr *a; XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_NODE(self)); g_return_if_fail(name != NULL); if (priv->attrs == NULL) priv->attrs = g_ptr_array_new_with_free_func((GDestroyNotify)xb_builder_node_attr_free); /* check for existing name */ for (guint i = 0; i < priv->attrs->len; i++) { a = g_ptr_array_index(priv->attrs, i); if (g_strcmp0(a->name, name) == 0) { g_free(a->value); a->value = g_strdup(value); return; } } /* create new */ a = g_slice_new0(XbBuilderNodeAttr); a->name = g_strdup(name); a->name_idx = XB_SILO_UNSET; a->value = g_strdup(value); a->value_idx = XB_SILO_UNSET; g_ptr_array_add(priv->attrs, a); } /** * xb_builder_node_remove_attr: * @self: a #XbBuilderNode * @name: attribute name, e.g. `type` * * Removes an attribute from the builder node. * * Since: 0.1.0 **/ void xb_builder_node_remove_attr(XbBuilderNode *self, const gchar *name) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_NODE(self)); g_return_if_fail(name != NULL); if (priv->attrs == NULL) return; for (guint i = 0; i < priv->attrs->len; i++) { XbBuilderNodeAttr *a = g_ptr_array_index(priv->attrs, i); if (g_strcmp0(a->name, name) == 0) { g_ptr_array_remove_index(priv->attrs, i); break; } } } /** * xb_builder_node_depth: * @self: a #XbBuilderNode * * Gets the depth of the node tree, where 0 is the root node. * * Since: 0.1.1 **/ guint xb_builder_node_depth(XbBuilderNode *self) { for (guint i = 0;; i++) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); if (priv->parent == NULL) return i; self = priv->parent; } return 0; } /** * xb_builder_node_add_child: * @self: A XbBuilderNode * @child: A XbBuilderNode * * Adds a child builder node. * * Since: 0.1.0 **/ void xb_builder_node_add_child(XbBuilderNode *self, XbBuilderNode *child) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); XbBuilderNodePrivate *priv_child = GET_PRIVATE(child); g_return_if_fail(XB_IS_BUILDER_NODE(self)); g_return_if_fail(XB_IS_BUILDER_NODE(child)); g_return_if_fail(priv_child->parent == NULL); /* no refcount */ priv_child->parent = self; if (priv->children == NULL) priv->children = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_ptr_array_add(priv->children, g_object_ref(child)); } /** * xb_builder_node_remove_child: * @self: A XbBuilderNode * @child: A XbBuilderNode * * Removes a child builder node. * * Since: 0.1.1 **/ void xb_builder_node_remove_child(XbBuilderNode *self, XbBuilderNode *child) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); XbBuilderNodePrivate *priv_child = GET_PRIVATE(child); /* no refcount */ priv_child->parent = NULL; if (priv->children != NULL) g_ptr_array_remove(priv->children, child); } /** * xb_builder_node_unlink: * @self: a #XbBuilderNode * * Unlinks a #XbBuilderNode from a tree, resulting in two separate trees. * * This should not be used from the function called by xb_builder_node_traverse() * otherwise the entire tree will not be traversed. * * Instead use xb_builder_node_add_flag(bn,XB_BUILDER_NODE_FLAG_IGNORE); * * Since: 0.1.1 **/ void xb_builder_node_unlink(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_NODE(self)); if (priv->parent == NULL) return; xb_builder_node_remove_child(priv->parent, self); } /** * xb_builder_node_get_parent: * @self: a #XbBuilderNode * * Gets the parent node for the current node. * * Returns: (transfer full): a new #XbBuilderNode, or %NULL no parent exists. * * Since: 0.1.1 **/ XbBuilderNode * xb_builder_node_get_parent(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL); if (priv->parent == NULL) return NULL; return g_object_ref(priv->parent); } /** * xb_builder_node_get_children: * @self: a #XbBuilderNode * * Gets the children of the builder node. * * Returns: (transfer none) (element-type XbBuilderNode): children * * Since: 0.1.0 **/ GPtrArray * xb_builder_node_get_children(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL); /* For backwards compatibility reasons we have to return a non-%NULL * array here. */ if (priv->children == NULL) priv->children = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); return priv->children; } /** * xb_builder_node_get_first_child: * @self: a #XbBuilderNode * * Gets the first child of the builder node. * * Returns: (transfer none): a #XbBuilderNode, or %NULL * * Since: 0.1.12 **/ XbBuilderNode * xb_builder_node_get_first_child(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL); if (priv->children == NULL || priv->children->len == 0) return NULL; return g_ptr_array_index(priv->children, 0); } /** * xb_builder_node_get_last_child: * @self: a #XbBuilderNode * * Gets the last child of the builder node. * * Returns: (transfer none): a #XbBuilderNode, or %NULL * * Since: 0.1.12 **/ XbBuilderNode * xb_builder_node_get_last_child(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL); if (priv->children == NULL || priv->children->len == 0) return NULL; return g_ptr_array_index(priv->children, priv->children->len - 1); } /** * xb_builder_node_get_child: * @self: a #XbBuilderNode * @element: An element name, e.g. "url" * @text: (allow-none): node text, e.g. "gimp.desktop" * * Finds a child builder node by the element name, and optionally text value. * * Returns: (transfer full): a new #XbBuilderNode, or %NULL if not found * * Since: 0.1.1 **/ XbBuilderNode * xb_builder_node_get_child(XbBuilderNode *self, const gchar *element, const gchar *text) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL); g_return_val_if_fail(element != NULL, NULL); if (priv->children == NULL) return NULL; for (guint i = 0; i < priv->children->len; i++) { XbBuilderNode *child = g_ptr_array_index(priv->children, i); if (g_strcmp0(xb_builder_node_get_element(child), element) != 0) continue; if (text != NULL && g_strcmp0(xb_builder_node_get_text(child), text) != 0) continue; return g_object_ref(child); } return NULL; } typedef struct { gint max_depth; XbBuilderNodeTraverseFunc func; gpointer user_data; GTraverseFlags flags; GTraverseType order; } XbBuilderNodeTraverseHelper; static void xb_builder_node_traverse_cb(XbBuilderNodeTraverseHelper *helper, XbBuilderNode *bn, gint depth) { XbBuilderNodePrivate *priv = GET_PRIVATE(bn); GPtrArray *children = priv->children; /* only leaves */ if (helper->flags == G_TRAVERSE_LEAVES && children != NULL && children->len > 0) return; /* only non-leaves */ if (helper->flags == G_TRAVERSE_NON_LEAVES && (children == NULL || children->len == 0)) return; /* recurse */ if (helper->order == G_PRE_ORDER) { if (helper->func(bn, helper->user_data)) return; } if ((helper->max_depth < 0 || depth < helper->max_depth) && children != NULL) { for (guint i = 0; i < children->len; i++) { XbBuilderNode *bc = g_ptr_array_index(children, i); xb_builder_node_traverse_cb(helper, bc, depth + 1); } } if (helper->order == G_POST_ORDER) { if (helper->func(bn, helper->user_data)) return; } } /** * xb_builder_node_traverse: * @self: a #XbBuilderNode * @order: a #GTraverseType, e.g. %G_PRE_ORDER * @flags: a #GTraverseFlags, e.g. %G_TRAVERSE_ALL * @max_depth: the maximum depth of the traversal, or -1 for no limit * @func: (scope call): a #XbBuilderNodeTraverseFunc * @user_data: user pointer to pass to @func, or %NULL * * Traverses a tree starting from @self. It calls the given function for each * node visited. * * The traversal can be halted at any point by returning TRUE from @func. * * Since: 0.1.1 **/ void xb_builder_node_traverse(XbBuilderNode *self, GTraverseType order, GTraverseFlags flags, gint max_depth, XbBuilderNodeTraverseFunc func, gpointer user_data) { XbBuilderNodeTraverseHelper helper = { .max_depth = max_depth, .order = order, .flags = flags, .func = func, .user_data = user_data, }; if (order == G_PRE_ORDER || order == G_POST_ORDER) { xb_builder_node_traverse_cb(&helper, self, 0); return; } g_critical("order %u not supported", order); } typedef struct { XbBuilderNodeSortFunc func; gpointer user_data; } XbBuilderNodeSortHelper; static gint xb_builder_node_sort_children_cb(gconstpointer a, gconstpointer b, gpointer user_data) { XbBuilderNodeSortHelper *helper = (XbBuilderNodeSortHelper *)user_data; XbBuilderNode *bn1 = *((XbBuilderNode **)a); XbBuilderNode *bn2 = *((XbBuilderNode **)b); return helper->func(bn1, bn2, helper->user_data); } /** * xb_builder_node_sort_children: * @self: a #XbBuilderNode * @func: (scope call): a #XbBuilderNodeSortFunc * @user_data: user pointer to pass to @func, or %NULL * * Sorts the node children using a custom sort function. * * Since: 0.1.3 **/ void xb_builder_node_sort_children(XbBuilderNode *self, XbBuilderNodeSortFunc func, gpointer user_data) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); XbBuilderNodeSortHelper helper = { .func = func, .user_data = user_data, }; g_return_if_fail(XB_IS_BUILDER_NODE(self)); g_return_if_fail(func != NULL); if (priv->children == NULL) return; g_ptr_array_sort_with_data(priv->children, xb_builder_node_sort_children_cb, &helper); } /* private */ guint32 xb_builder_node_get_offset(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), 0); return priv->offset; } /* private */ void xb_builder_node_set_offset(XbBuilderNode *self, guint32 offset) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_NODE(self)); priv->offset = offset; } /* private */ gint xb_builder_node_get_priority(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), 0); return priv->priority; } /* private */ void xb_builder_node_set_priority(XbBuilderNode *self, gint priority) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_NODE(self)); priv->priority = priority; } /* private */ guint32 xb_builder_node_get_element_idx(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), 0); return priv->element_idx; } /* private */ void xb_builder_node_set_element_idx(XbBuilderNode *self, guint32 element_idx) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_NODE(self)); priv->element_idx = element_idx; } /* private */ guint32 xb_builder_node_get_text_idx(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), 0); return priv->text_idx; } /* private */ void xb_builder_node_set_text_idx(XbBuilderNode *self, guint32 text_idx) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_NODE(self)); priv->text_idx = text_idx; } /* private */ guint32 xb_builder_node_get_tail_idx(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_NODE(self), 0); return priv->tail_idx; } /* private */ void xb_builder_node_set_tail_idx(XbBuilderNode *self, guint32 tail_idx) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_NODE(self)); priv->tail_idx = tail_idx; } /* private */ guint32 xb_builder_node_size(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); guint32 sz = sizeof(XbSiloNode); gsize attr_len = (priv->attrs != NULL) ? priv->attrs->len : 0; gsize token_len = (priv->tokens != NULL) ? MIN(priv->tokens->len, XB_OPCODE_TOKEN_MAX) : 0; return sz + attr_len * sizeof(XbSiloNodeAttr) + token_len * sizeof(guint32); } static void xb_builder_node_attr_free(XbBuilderNodeAttr *attr) { g_free(attr->name); g_free(attr->value); g_slice_free(XbBuilderNodeAttr, attr); } static void xb_builder_node_init(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); priv->element_idx = XB_SILO_UNSET; priv->text_idx = XB_SILO_UNSET; priv->tail_idx = XB_SILO_UNSET; priv->attrs = NULL; /* only allocated when an attribute is added */ priv->children = NULL; /* only allocated when a child is added */ } static void xb_builder_node_dispose(GObject *obj) { XbBuilderNode *self = XB_BUILDER_NODE(obj); XbBuilderNodePrivate *priv = GET_PRIVATE(self); /* clear all the child nodes’ parent pointers */ if (priv->children != NULL) { for (guint i = 0; i < priv->children->len; i++) { XbBuilderNode *child = g_ptr_array_index(priv->children, i); XbBuilderNodePrivate *priv_child = GET_PRIVATE(child); priv_child->parent = NULL; } } G_OBJECT_CLASS(xb_builder_node_parent_class)->dispose(obj); } static void xb_builder_node_finalize(GObject *obj) { XbBuilderNode *self = XB_BUILDER_NODE(obj); XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_free(priv->element); g_free(priv->text); g_free(priv->tail); g_clear_pointer(&priv->attrs, g_ptr_array_unref); g_clear_pointer(&priv->children, g_ptr_array_unref); g_clear_pointer(&priv->tokens, g_ptr_array_unref); g_clear_pointer(&priv->token_idxs, g_array_unref); G_OBJECT_CLASS(xb_builder_node_parent_class)->finalize(obj); } static void xb_builder_node_class_init(XbBuilderNodeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->dispose = xb_builder_node_dispose; object_class->finalize = xb_builder_node_finalize; } /** * xb_builder_node_new: * @element: An element name, e.g. "component" * * Creates a new builder node. * * Returns: (transfer full): a new #XbBuilderNode * * Since: 0.1.0 **/ XbBuilderNode * xb_builder_node_new(const gchar *element) { XbBuilderNode *self = g_object_new(XB_TYPE_BUILDER_NODE, NULL); XbBuilderNodePrivate *priv = GET_PRIVATE(self); priv->element = g_strdup(element); return self; } /** * xb_builder_node_insert: (skip) * @parent: A XbBuilderNode, or %NULL * @element: An element name, e.g. "component" * @...: any attributes to add to the node, terminated by %NULL * * Creates a new builder node. * * Returns: (transfer full): a new #XbBuilderNode * * Since: 0.1.0 **/ XbBuilderNode * xb_builder_node_insert(XbBuilderNode *parent, const gchar *element, ...) { XbBuilderNode *self = xb_builder_node_new(element); va_list args; const gchar *key; const gchar *value; /* add this node to the parent */ if (parent != NULL) xb_builder_node_add_child(parent, self); /* process the attrs valist */ va_start(args, element); for (guint i = 0;; i++) { key = va_arg(args, const gchar *); if (key == NULL) break; value = va_arg(args, const gchar *); if (value == NULL) break; xb_builder_node_set_attr(self, key, value); } va_end(args); return self; } /** * xb_builder_node_insert_text: (skip) * @parent: A XbBuilderNode, or %NULL * @element: An element name, e.g. "id" * @text: (allow-none): node text, e.g. "gimp.desktop" * @...: any attributes to add to the node, terminated by %NULL * * Creates a new builder node with optional node text. * * Since: 0.1.0 **/ void xb_builder_node_insert_text(XbBuilderNode *parent, const gchar *element, const gchar *text, ...) { g_autoptr(XbBuilderNode) self = xb_builder_node_new(element); va_list args; const gchar *key; const gchar *value; g_return_if_fail(parent != NULL); /* add this node to the parent */ xb_builder_node_add_child(parent, self); if (text != NULL) xb_builder_node_set_text(self, text, -1); /* process the attrs valist */ va_start(args, text); for (guint i = 0;; i++) { key = va_arg(args, const gchar *); if (key == NULL) break; value = va_arg(args, const gchar *); if (value == NULL) break; xb_builder_node_set_attr(self, key, value); } va_end(args); } typedef struct { GString *xml; XbNodeExportFlags flags; guint level; } XbBuilderNodeExportHelper; static gboolean xb_builder_node_export_helper(XbBuilderNode *self, XbBuilderNodeExportHelper *helper, GError **error) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); /* do not output */ if (xb_builder_node_has_flag(self, XB_BUILDER_NODE_FLAG_IGNORE)) return TRUE; /* add start of opening tag */ if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) { for (guint i = 0; i < helper->level; i++) g_string_append(helper->xml, " "); } g_string_append_printf(helper->xml, "<%s", priv->element); /* add any attributes */ for (guint i = 0; priv->attrs != NULL && i < priv->attrs->len; i++) { XbBuilderNodeAttr *a = g_ptr_array_index(priv->attrs, i); g_autofree gchar *key = xb_string_xml_escape(a->name); g_autofree gchar *val = xb_string_xml_escape(a->value); g_string_append_printf(helper->xml, " %s=\"%s\"", key, val); } if (helper->flags & XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY && priv->text == NULL && priv->children == NULL) { g_string_append(helper->xml, " />"); } else { /* finish the opening tag and add any text if it exists */ if (priv->text != NULL) { g_autofree gchar *text = xb_string_xml_escape(priv->text); g_string_append(helper->xml, ">"); g_string_append(helper->xml, text); } else { g_string_append(helper->xml, ">"); if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE) g_string_append(helper->xml, "\n"); } /* recurse deeper */ for (guint i = 0; priv->children != NULL && i < priv->children->len; i++) { XbBuilderNode *child = g_ptr_array_index(priv->children, i); helper->level++; if (!xb_builder_node_export_helper(child, helper, error)) return FALSE; helper->level--; } /* add closing tag */ if ((helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) > 0 && priv->text == NULL) { for (guint i = 0; i < helper->level; i++) g_string_append(helper->xml, " "); } g_string_append_printf(helper->xml, "", priv->element); } /* add any tail if it exists */ if (priv->tail != NULL) { g_autofree gchar *tail = xb_string_xml_escape(priv->tail); g_string_append(helper->xml, tail); } if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE) g_string_append(helper->xml, "\n"); return TRUE; } /** * xb_builder_node_export: * @self: a #XbBuilderNode * @flags: some #XbNodeExportFlags, e.g. #XB_NODE_EXPORT_FLAG_NONE * @error: the #GError, or %NULL * * Exports the node to XML. * * Returns: XML data, or %NULL for an error * * Since: 0.1.5 **/ gchar * xb_builder_node_export(XbBuilderNode *self, XbNodeExportFlags flags, GError **error) { g_autoptr(GString) xml = g_string_new(NULL); XbBuilderNodeExportHelper helper = { .flags = flags, .level = 0, .xml = xml, }; g_return_val_if_fail(XB_IS_BUILDER_NODE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if ((flags & XB_NODE_EXPORT_FLAG_ADD_HEADER) > 0) g_string_append(xml, "\n"); if (!xb_builder_node_export_helper(self, &helper, error)) return NULL; return g_string_free(g_steal_pointer(&xml), FALSE); } /** * xb_builder_node_add_token: * @self: a #XbBuilderNode * @token: a new token * * Adds a token to the builder node. * * Since: 0.3.1 **/ void xb_builder_node_add_token(XbBuilderNode *self, const gchar *token) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(self != NULL); g_return_if_fail(token != NULL); if (priv->tokens == NULL) priv->tokens = g_ptr_array_new_with_free_func(g_free); g_ptr_array_add(priv->tokens, g_strdup(token)); } /** * xb_builder_node_get_tokens: * @self: a #XbBuilderNode * * Gets the tokens of the builder node. * * Returns: (transfer none) (element-type utf8) (nullable): tokens * * Since: 0.3.1 **/ GPtrArray * xb_builder_node_get_tokens(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(self != NULL, NULL); return priv->tokens; } /* private */ void xb_builder_node_add_token_idx(XbBuilderNode *self, guint32 tail_idx) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(self != NULL); g_return_if_fail(tail_idx != XB_SILO_UNSET); if (priv->token_idxs == NULL) priv->token_idxs = g_array_new(FALSE, FALSE, sizeof(guint32)); g_array_append_val(priv->token_idxs, tail_idx); } /* Returns: (transfer none) (element-type guint32) (nullable): token indexes */ GArray * xb_builder_node_get_token_idxs(XbBuilderNode *self) { XbBuilderNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(self != NULL, NULL); return priv->token_idxs; } libxmlb-0.3.22/src/xb-builder-node.h000066400000000000000000000117431476425255200171710ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-compile.h" #include "xb-node.h" G_BEGIN_DECLS #define XB_TYPE_BUILDER_NODE (xb_builder_node_get_type()) G_DECLARE_DERIVABLE_TYPE(XbBuilderNode, xb_builder_node, XB, BUILDER_NODE, GObject) struct _XbBuilderNodeClass { GObjectClass parent_class; /*< private >*/ void (*_xb_reserved1)(void); void (*_xb_reserved2)(void); void (*_xb_reserved3)(void); void (*_xb_reserved4)(void); void (*_xb_reserved5)(void); void (*_xb_reserved6)(void); void (*_xb_reserved7)(void); }; /** * XbBuilderNodeFlags: * @XB_BUILDER_NODE_FLAG_NONE: No extra flags to use * @XB_BUILDER_NODE_FLAG_IGNORE: Do not include this node in the silo * @XB_BUILDER_NODE_FLAG_LITERAL_TEXT: Assume the node CDATA is already valid * @XB_BUILDER_NODE_FLAG_HAS_TEXT: If the node has leading text * @XB_BUILDER_NODE_FLAG_HAS_TAIL: If the node has trailing text * @XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT: Tokenize and fold text to ASCII (Since: 0.3.1) * @XB_BUILDER_NODE_FLAG_STRIP_TEXT: Strip leading and trailing spaces from text (Since: *0.3.4) * * The flags used when building a node. **/ typedef enum { XB_BUILDER_NODE_FLAG_NONE = 0, /* Since: 0.1.0 */ XB_BUILDER_NODE_FLAG_IGNORE = 1 << 0, /* Since: 0.1.0 */ XB_BUILDER_NODE_FLAG_LITERAL_TEXT = 1 << 1, /* Since: 0.1.0 */ XB_BUILDER_NODE_FLAG_HAS_TEXT = 1 << 2, /* Since: 0.1.12 */ XB_BUILDER_NODE_FLAG_HAS_TAIL = 1 << 3, /* Since: 0.1.12 */ XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT = 1 << 4, /* Since: 0.3.1 */ XB_BUILDER_NODE_FLAG_STRIP_TEXT = 1 << 5, /* Since: 0.3.4 */ /*< private >*/ XB_BUILDER_NODE_FLAG_LAST } XbBuilderNodeFlags; typedef gboolean (*XbBuilderNodeTraverseFunc)(XbBuilderNode *bn, gpointer user_data); typedef gint (*XbBuilderNodeSortFunc)(XbBuilderNode *bn1, XbBuilderNode *bn2, gpointer user_data); XbBuilderNode * xb_builder_node_new(const gchar *element); XbBuilderNode * xb_builder_node_insert(XbBuilderNode *parent, const gchar *element, ...) G_GNUC_NULL_TERMINATED G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(2); void xb_builder_node_insert_text(XbBuilderNode *parent, const gchar *element, const gchar *text, ...) G_GNUC_NULL_TERMINATED G_GNUC_NON_NULL(1, 2); gboolean xb_builder_node_has_flag(XbBuilderNode *self, XbBuilderNodeFlags flag) G_GNUC_NON_NULL(1); void xb_builder_node_add_flag(XbBuilderNode *self, XbBuilderNodeFlags flag) G_GNUC_NON_NULL(1); const gchar * xb_builder_node_get_element(XbBuilderNode *self) G_GNUC_NON_NULL(1); void xb_builder_node_set_element(XbBuilderNode *self, const gchar *element) G_GNUC_NON_NULL(1); const gchar * xb_builder_node_get_text(XbBuilderNode *self) G_GNUC_NON_NULL(1); guint64 xb_builder_node_get_text_as_uint(XbBuilderNode *self) G_GNUC_NON_NULL(1); void xb_builder_node_set_text(XbBuilderNode *self, const gchar *text, gssize text_len) G_GNUC_NON_NULL(1); void xb_builder_node_tokenize_text(XbBuilderNode *self) G_GNUC_NON_NULL(1); const gchar * xb_builder_node_get_tail(XbBuilderNode *self) G_GNUC_NON_NULL(1); void xb_builder_node_set_tail(XbBuilderNode *self, const gchar *tail, gssize tail_len) G_GNUC_NON_NULL(1); const gchar * xb_builder_node_get_attr(XbBuilderNode *self, const gchar *name) G_GNUC_NON_NULL(1, 2); guint64 xb_builder_node_get_attr_as_uint(XbBuilderNode *self, const gchar *name) G_GNUC_NON_NULL(1, 2); void xb_builder_node_set_attr(XbBuilderNode *self, const gchar *name, const gchar *value) G_GNUC_NON_NULL(1, 2); void xb_builder_node_remove_attr(XbBuilderNode *self, const gchar *name) G_GNUC_NON_NULL(1); void xb_builder_node_add_child(XbBuilderNode *self, XbBuilderNode *child) G_GNUC_NON_NULL(2); void xb_builder_node_remove_child(XbBuilderNode *self, XbBuilderNode *child) G_GNUC_NON_NULL(1); GPtrArray * xb_builder_node_get_children(XbBuilderNode *self) G_GNUC_NON_NULL(1); XbBuilderNode * xb_builder_node_get_first_child(XbBuilderNode *self) G_GNUC_NON_NULL(1); XbBuilderNode * xb_builder_node_get_last_child(XbBuilderNode *self) G_GNUC_NON_NULL(1); XbBuilderNode * xb_builder_node_get_child(XbBuilderNode *self, const gchar *element, const gchar *text) G_GNUC_NON_NULL(1); void xb_builder_node_unlink(XbBuilderNode *self) G_GNUC_NON_NULL(1); XbBuilderNode * xb_builder_node_get_parent(XbBuilderNode *self) G_GNUC_NON_NULL(1); guint xb_builder_node_depth(XbBuilderNode *self) G_GNUC_NON_NULL(1); void xb_builder_node_traverse(XbBuilderNode *self, GTraverseType order, GTraverseFlags flags, gint max_depth, XbBuilderNodeTraverseFunc func, gpointer user_data) G_GNUC_NON_NULL(1, 5); void xb_builder_node_sort_children(XbBuilderNode *self, XbBuilderNodeSortFunc func, gpointer user_data) G_GNUC_NON_NULL(1, 2); gchar * xb_builder_node_export(XbBuilderNode *self, XbNodeExportFlags flags, GError **error) G_GNUC_NON_NULL(1); GPtrArray * xb_builder_node_get_tokens(XbBuilderNode *self) G_GNUC_NON_NULL(1); void xb_builder_node_add_token(XbBuilderNode *self, const gchar *token) G_GNUC_NON_NULL(1, 2); G_END_DECLS libxmlb-0.3.22/src/xb-builder-source-ctx-private.h000066400000000000000000000010641476425255200220030ustar00rootroot00000000000000/* * Copyright 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-builder-source-ctx.h" G_BEGIN_DECLS XbBuilderSourceCtx * xb_builder_source_ctx_new(GFile *file, GInputStream *istream) G_GNUC_NON_NULL(2); void xb_builder_source_ctx_set_filename(XbBuilderSourceCtx *self, const gchar *basename) G_GNUC_NON_NULL(1); gchar * xb_builder_source_ctx_get_content_type(XbBuilderSourceCtx *self, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-builder-source-ctx.c000066400000000000000000000150441476425255200203310ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbSilo" #include "config.h" #include #include "xb-builder-source-ctx-private.h" #include "xb-common-private.h" typedef struct { GFile *file; GInputStream *istream; gchar *basename; gchar *content_type; } XbBuilderSourceCtxPrivate; G_DEFINE_TYPE_WITH_PRIVATE(XbBuilderSourceCtx, xb_builder_source_ctx, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_builder_source_ctx_get_instance_private(o)) /** * xb_builder_source_ctx_get_stream: * @self: a #XbBuilderSourceCtx * * Returns the input stream currently being processed. * * Returns: (transfer none): a #GInputStream * * Since: 0.1.7 **/ GInputStream * xb_builder_source_ctx_get_stream(XbBuilderSourceCtx *self) { XbBuilderSourceCtxPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_SOURCE_CTX(self), NULL); return priv->istream; } static GBytes * _g_input_stream_read_bytes_in_chunks(GInputStream *stream, gsize count, gsize chunk_sz, GCancellable *cancellable, GError **error) { g_autofree guint8 *tmp = NULL; g_autoptr(GByteArray) buf = g_byte_array_new(); g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); g_return_val_if_fail(count > 0, NULL); g_return_val_if_fail(chunk_sz > 0, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* read from stream in chunks */ tmp = g_malloc(chunk_sz); while (TRUE) { gssize sz; sz = g_input_stream_read(stream, tmp, chunk_sz, NULL, error); if (sz == 0) break; if (sz < 0) return NULL; g_byte_array_append(buf, tmp, sz); if (buf->len > count) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot read from fd: 0x%x > 0x%x", buf->len, (guint)count); return NULL; } } return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); } /** * xb_builder_source_ctx_get_bytes: * @self: a #XbBuilderSourceCtx * @cancellable: a #GCancellable, or %NULL * @error: the #GError, or %NULL * * Returns the data currently being processed. * * If the #XbBuilderSourceCtx is backed by a file, the returned #GBytes may be * memory-mapped, and the backing file must not be modified until the #GBytes is * destroyed. * * Returns: (transfer full): a #GBytes * * Since: 0.1.7 **/ GBytes * xb_builder_source_ctx_get_bytes(XbBuilderSourceCtx *self, GCancellable *cancellable, GError **error) { XbBuilderSourceCtxPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_SOURCE_CTX(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* Try mmap()ing the file first, as that avoids buffer allocation. * Note that this imposes the restriction that the backing file must not * be modified during the lifetime of the returned #GBytes. */ if (priv->file != NULL) { g_autoptr(GMappedFile) mapped_file = NULL; g_autofree gchar *filename = g_file_get_path(priv->file); mapped_file = g_mapped_file_new(filename, FALSE, NULL); if (mapped_file != NULL) return g_mapped_file_get_bytes(mapped_file); } return _g_input_stream_read_bytes_in_chunks(priv->istream, 128 * 1024 * 1024, /* 128Mb */ 32 * 1024, /* 32Kb */ cancellable, error); } /** * xb_builder_source_ctx_get_filename: * @self: a #XbBuilderSourceCtx * * Returns the basename of the file currently being processed. * * Returns: (transfer none) (nullable): a basename, or %NULL if unset * * Since: 0.1.7 **/ const gchar * xb_builder_source_ctx_get_filename(XbBuilderSourceCtx *self) { XbBuilderSourceCtxPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_SOURCE_CTX(self), NULL); return priv->basename; } /** * xb_builder_source_ctx_get_content_type: * @self: a #XbBuilderSourceCtx * @cancellable: a #GCancellable, or %NULL * @error: the #GError, or %NULL * * Returns the content type of the input stream currently being * processed. * * Returns: (transfer full): a content type (e.g. `application/x-desktop`), or %NULL on error * * Since: 0.1.7 **/ gchar * xb_builder_source_ctx_get_content_type(XbBuilderSourceCtx *self, GCancellable *cancellable, GError **error) { XbBuilderSourceCtxPrivate *priv = GET_PRIVATE(self); gsize bufsz = 0; guchar buf[4096] = {0x00}; g_return_val_if_fail(XB_IS_BUILDER_SOURCE_CTX(self), NULL); if (G_IS_SEEKABLE(priv->istream)) { if (!g_input_stream_read_all(priv->istream, buf, sizeof(buf), &bufsz, cancellable, error)) return NULL; if (!g_seekable_seek(G_SEEKABLE(priv->istream), 0, G_SEEK_SET, cancellable, error)) return NULL; } if (bufsz > 0) return xb_content_type_guess(priv->basename, buf, bufsz); return xb_content_type_guess(priv->basename, NULL, 0); } /* private */ void xb_builder_source_ctx_set_filename(XbBuilderSourceCtx *self, const gchar *basename) { XbBuilderSourceCtxPrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_SOURCE_CTX(self)); g_return_if_fail(basename != NULL); g_free(priv->basename); priv->basename = g_strdup(basename); } static void xb_builder_source_ctx_init(XbBuilderSourceCtx *self) { } static void xb_builder_source_ctx_finalize(GObject *obj) { XbBuilderSourceCtx *self = XB_BUILDER_SOURCE_CTX(obj); XbBuilderSourceCtxPrivate *priv = GET_PRIVATE(self); g_free(priv->basename); g_object_unref(priv->istream); g_clear_object(&priv->file); G_OBJECT_CLASS(xb_builder_source_ctx_parent_class)->finalize(obj); } static void xb_builder_source_ctx_class_init(XbBuilderSourceCtxClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = xb_builder_source_ctx_finalize; } /** * xb_builder_source_ctx_new: * @file: (transfer none) (nullable): Path to the file which contains the source, * or %NULL if the source is not directly loadable from disk * @istream: (transfer none): Input stream to load the source from * * Creates a new builder source_ctx. * * Returns: (transfer full): a new #XbBuilderSourceCtx * * Since: 0.1.7 **/ XbBuilderSourceCtx * xb_builder_source_ctx_new(GFile *file, GInputStream *istream) { XbBuilderSourceCtx *self = g_object_new(XB_TYPE_BUILDER_SOURCE_CTX, NULL); XbBuilderSourceCtxPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(file == NULL || G_IS_FILE(file), NULL); g_return_val_if_fail(G_IS_INPUT_STREAM(istream), NULL); priv->file = (file != NULL) ? g_object_ref(file) : NULL; priv->istream = g_object_ref(istream); return self; } libxmlb-0.3.22/src/xb-builder-source-ctx.h000066400000000000000000000017071476425255200203370ustar00rootroot00000000000000/* * Copyright 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-compile.h" G_BEGIN_DECLS #define XB_TYPE_BUILDER_SOURCE_CTX (xb_builder_source_ctx_get_type()) G_DECLARE_DERIVABLE_TYPE(XbBuilderSourceCtx, xb_builder_source_ctx, XB, BUILDER_SOURCE_CTX, GObject) struct _XbBuilderSourceCtxClass { GObjectClass parent_class; /*< private >*/ void (*_xb_reserved1)(void); void (*_xb_reserved2)(void); void (*_xb_reserved3)(void); void (*_xb_reserved4)(void); void (*_xb_reserved5)(void); void (*_xb_reserved6)(void); void (*_xb_reserved7)(void); }; GInputStream * xb_builder_source_ctx_get_stream(XbBuilderSourceCtx *self) G_GNUC_NON_NULL(1); const gchar * xb_builder_source_ctx_get_filename(XbBuilderSourceCtx *self) G_GNUC_NON_NULL(1); GBytes * xb_builder_source_ctx_get_bytes(XbBuilderSourceCtx *self, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-builder-source-private.h000066400000000000000000000015701476425255200212110ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-builder-node.h" #include "xb-builder-source.h" G_BEGIN_DECLS XbBuilderNode * xb_builder_source_get_info(XbBuilderSource *self) G_GNUC_NON_NULL(1); gchar * xb_builder_source_get_guid(XbBuilderSource *self) G_GNUC_NON_NULL(1); const gchar * xb_builder_source_get_prefix(XbBuilderSource *self) G_GNUC_NON_NULL(1); GInputStream * xb_builder_source_get_istream(XbBuilderSource *self, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1); GFile * xb_builder_source_get_file(XbBuilderSource *self) G_GNUC_NON_NULL(1); gboolean xb_builder_source_fixup(XbBuilderSource *self, XbBuilderNode *bn, GError **error) G_GNUC_NON_NULL(1, 2); XbBuilderSourceFlags xb_builder_source_get_flags(XbBuilderSource *self) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-builder-source.c000066400000000000000000000440011476425255200175300ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbSilo" #include "config.h" #include #include #include "xb-builder-fixup-private.h" #include "xb-builder-source-ctx-private.h" #include "xb-builder-source-private.h" #ifdef HAVE_LZMA #include "xb-lzma-decompressor.h" #endif #ifdef HAVE_ZSTD #include "xb-zstd-decompressor.h" #endif typedef struct { GInputStream *istream; GFile *file; GPtrArray *fixups; /* of XbBuilderFixup */ GPtrArray *adapters; /* of XbBuilderSourceAdapter */ XbBuilderNode *info; gchar *guid; gchar *prefix; gchar *content_type; XbBuilderSourceFlags flags; } XbBuilderSourcePrivate; G_DEFINE_TYPE_WITH_PRIVATE(XbBuilderSource, xb_builder_source, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_builder_source_get_instance_private(o)) typedef struct { gchar *content_type; XbBuilderSourceAdapterFunc func_adapter; gpointer user_data; GDestroyNotify user_data_free; gboolean is_simple; } XbBuilderSourceAdapter; static XbBuilderSourceAdapter * xb_builder_source_get_adapter_by_mime(XbBuilderSource *self, const gchar *content_type) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); for (guint i = 0; i < priv->adapters->len; i++) { XbBuilderSourceAdapter *item = g_ptr_array_index(priv->adapters, i); if (item->func_adapter == NULL) continue; if (g_strcmp0(item->content_type, content_type) == 0) return item; } return NULL; } /** * xb_builder_source_load_file: * @self: a #XbBuilderSource * @file: a #GFile * @flags: some #XbBuilderSourceFlags, e.g. %XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT * @cancellable: a #GCancellable, or %NULL * @error: the #GError, or %NULL * * Loads an optionally compressed XML file to build a #XbSilo. * * Returns: %TRUE for success * * Since: 0.1.1 **/ gboolean xb_builder_source_load_file(XbBuilderSource *self, GFile *file, XbBuilderSourceFlags flags, GCancellable *cancellable, GError **error) { const gchar *content_type = NULL; guint32 ctime_usec; guint64 ctime; g_autofree gchar *fn = NULL; g_autoptr(GFileInfo) fileinfo = NULL; g_autoptr(GString) guid = NULL; XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_SOURCE(self), FALSE); g_return_val_if_fail(G_IS_FILE(file), FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* what kind of file is this */ fileinfo = g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," G_FILE_ATTRIBUTE_TIME_CHANGED "," G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, G_FILE_QUERY_INFO_NONE, cancellable, error); if (fileinfo == NULL) return FALSE; /* add data to GUID */ fn = g_file_get_path(file); guid = g_string_new(fn); ctime = g_file_info_get_attribute_uint64(fileinfo, G_FILE_ATTRIBUTE_TIME_CHANGED); if (ctime != 0) g_string_append_printf(guid, ":ctime=%" G_GUINT64_FORMAT, ctime); ctime_usec = g_file_info_get_attribute_uint32(fileinfo, G_FILE_ATTRIBUTE_TIME_CHANGED_USEC); if (ctime_usec != 0) g_string_append_printf(guid, ".%" G_GUINT32_FORMAT, ctime_usec); priv->guid = g_string_free(g_steal_pointer(&guid), FALSE); /* check content type of file */ content_type = g_file_info_get_attribute_string(fileinfo, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE); if (content_type == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot get content type for file"); return FALSE; } /* success */ priv->flags = flags; priv->content_type = g_strdup(content_type); priv->file = g_object_ref(file); return TRUE; } /** * xb_builder_source_set_info: * @self: a #XbBuilderSource * @info: (allow-none): a #XbBuilderNode * * Sets an optional information metadata node on the root node. * * Since: 0.1.0 **/ void xb_builder_source_set_info(XbBuilderSource *self, XbBuilderNode *info) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_SOURCE(self)); g_set_object(&priv->info, info); } /** * xb_builder_source_set_prefix: * @self: a #XbBuilderSource * @prefix: (allow-none): an XPath prefix, e.g. `installed` * * Sets an optional prefix on the root node. This makes any nodes added * using this source reside under a common shared parent node. * * Since: 0.1.0 **/ void xb_builder_source_set_prefix(XbBuilderSource *self, const gchar *prefix) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_SOURCE(self)); g_free(priv->prefix); priv->prefix = g_strdup(prefix); } /** * xb_builder_source_load_xml: * @self: a #XbBuilderSource * @xml: XML data * @flags: some #XbBuilderSourceFlags, e.g. %XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT * @error: the #GError, or %NULL * * Loads XML data and begins to build a #XbSilo. * * Returns: %TRUE for success * * Since: 0.1.1 **/ gboolean xb_builder_source_load_xml(XbBuilderSource *self, const gchar *xml, XbBuilderSourceFlags flags, GError **error) { g_autoptr(GBytes) blob = NULL; g_autoptr(GChecksum) csum = g_checksum_new(G_CHECKSUM_SHA1); XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_SOURCE(self), FALSE); g_return_val_if_fail(xml != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* add a GUID of the SHA1 hash of the entire string */ g_checksum_update(csum, (const guchar *)xml, -1); priv->guid = g_strdup(g_checksum_get_string(csum)); /* create input stream */ blob = g_bytes_new(xml, strlen(xml)); priv->istream = g_memory_input_stream_new_from_bytes(blob); if (priv->istream == NULL) return FALSE; /* success */ priv->flags = flags; return TRUE; } /** * xb_builder_source_load_bytes: * @self: a #XbBuilderSource * @bytes: a #GBytes * @flags: some #XbBuilderSourceFlags, e.g. %XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT * @error: the #GError, or %NULL * * Loads XML data and begins to build a #XbSilo. * * Returns: %TRUE for success * * Since: 0.1.2 **/ gboolean xb_builder_source_load_bytes(XbBuilderSource *self, GBytes *bytes, XbBuilderSourceFlags flags, GError **error) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_autoptr(GChecksum) csum = g_checksum_new(G_CHECKSUM_SHA1); g_return_val_if_fail(XB_IS_BUILDER_SOURCE(self), FALSE); g_return_val_if_fail(bytes != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* add a GUID of the SHA1 hash of the entire blob */ g_checksum_update(csum, (const guchar *)g_bytes_get_data(bytes, NULL), (gssize)g_bytes_get_size(bytes)); priv->guid = g_strdup(g_checksum_get_string(csum)); /* create input stream */ priv->istream = g_memory_input_stream_new_from_bytes(bytes); if (priv->istream == NULL) return FALSE; /* success */ priv->flags = flags; return TRUE; } /** * xb_builder_source_add_fixup: * @self: a #XbBuilderSource * @fixup: a #XbBuilderFixup * * Adds a function that will get run on every #XbBuilderNode compile creates * with this source. * * Since: 0.1.3 **/ void xb_builder_source_add_fixup(XbBuilderSource *self, XbBuilderFixup *fixup) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER_SOURCE(self)); g_return_if_fail(XB_IS_BUILDER_FIXUP(fixup)); g_ptr_array_add(priv->fixups, g_object_ref(fixup)); } static void xb_builder_source_init_adapter(XbBuilderSource *self, const gchar *content_types, XbBuilderSourceAdapterFunc func, gpointer user_data, GDestroyNotify user_data_free, gboolean is_simple) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_auto(GStrv) split = NULL; g_return_if_fail(XB_IS_BUILDER_SOURCE(self)); g_return_if_fail(content_types != NULL); g_return_if_fail(func != NULL); /* add each */ split = g_strsplit(content_types, ",", -1); for (guint i = 0; split[i] != NULL; i++) { XbBuilderSourceAdapter *item; item = g_slice_new0(XbBuilderSourceAdapter); item->content_type = g_strdup(split[i]); item->func_adapter = func; item->user_data = user_data; item->user_data_free = user_data_free; item->is_simple = is_simple; g_ptr_array_add(priv->adapters, item); } } /** * xb_builder_source_add_adapter: * @self: a #XbBuilderSource * @content_types: mimetypes, e.g. `application/x-desktop,application/gzip` * @func: a callback, or %NULL * @user_data: user pointer to pass to @func, or %NULL * @user_data_free: a function which gets called to free @user_data, or %NULL * * Adds a function that can be used to convert streams loaded with * xb_builder_source_load_xml(). * * This will decompress multiple layers of content, for instance decompressing a gzip stream into a * different content type that can then be parsed. Use xb_builder_source_add_simple_adapter() when * this recursive behaviour is not desired. * * Since: 0.1.7 **/ void xb_builder_source_add_adapter(XbBuilderSource *self, const gchar *content_types, XbBuilderSourceAdapterFunc func, gpointer user_data, GDestroyNotify user_data_free) { xb_builder_source_init_adapter(self, content_types, func, user_data, user_data_free, FALSE); } /** * xb_builder_source_add_simple_adapter: * @self: a #XbBuilderSource * @content_types: mimetypes, e.g. `application/x-desktop,application/gzip` * @func: a callback, or %NULL * @user_data: user pointer to pass to @func, or %NULL * @user_data_free: a function which gets called to free @user_data, or %NULL * * Adds a function that can be used to convert streams loaded with * xb_builder_source_load_xml(). * * This function is similar to xb_builder_source_add_adapter() but is limited to one "layer" of * content, for instance handling application/xml or a single simple type added using * xb_builder_source_add_adapter(). * * Since: 0.1.15 **/ void xb_builder_source_add_simple_adapter(XbBuilderSource *self, const gchar *content_types, XbBuilderSourceAdapterFunc func, gpointer user_data, GDestroyNotify user_data_free) { xb_builder_source_init_adapter(self, content_types, func, user_data, user_data_free, TRUE); } gboolean xb_builder_source_fixup(XbBuilderSource *self, XbBuilderNode *bn, GError **error) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); for (guint i = 0; i < priv->fixups->len; i++) { XbBuilderFixup *fixup = g_ptr_array_index(priv->fixups, i); if (!xb_builder_fixup_node(fixup, bn, error)) return FALSE; } return TRUE; } static gboolean xb_builder_source_info_guid_cb(XbBuilderNode *bn, gpointer data) { GString *str = (GString *)data; if (xb_builder_node_get_text(bn) != NULL) { g_string_append_printf(str, ":%s=%s", xb_builder_node_get_element(bn), xb_builder_node_get_text(bn)); } return FALSE; } gchar * xb_builder_source_get_guid(XbBuilderSource *self) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_autoptr(GString) str = g_string_new(priv->guid); g_return_val_if_fail(XB_IS_BUILDER_SOURCE(self), NULL); /* append function IDs */ for (guint i = 0; i < priv->fixups->len; i++) { XbBuilderFixup *fixup = g_ptr_array_index(priv->fixups, i); g_autofree gchar *tmp = xb_builder_fixup_get_guid(fixup); g_string_append_printf(str, ":%s", tmp); } /* append any info */ if (priv->info != NULL) { xb_builder_node_traverse(priv->info, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xb_builder_source_info_guid_cb, str); } /* append prefix */ if (priv->prefix != NULL) g_string_append_printf(str, ":prefix=%s", priv->prefix); return g_string_free(g_steal_pointer(&str), FALSE); } const gchar * xb_builder_source_get_prefix(XbBuilderSource *self) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_SOURCE(self), NULL); return priv->prefix; } XbBuilderNode * xb_builder_source_get_info(XbBuilderSource *self) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_SOURCE(self), NULL); return priv->info; } /* converts foo.xml.gz to foo.xml */ static void xb_builder_source_remove_last_extension(gchar *basename) { gchar *tmp = g_strrstr(basename, "."); if (tmp != NULL) *tmp = '\0'; } GInputStream * xb_builder_source_get_istream(XbBuilderSource *self, GCancellable *cancellable, GError **error) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_autofree gchar *basename = NULL; g_autoptr(GInputStream) istream = NULL; GFile *file; g_return_val_if_fail(XB_IS_BUILDER_SOURCE(self), NULL); /* nothing required */ if (priv->istream != NULL) return g_object_ref(priv->istream); /* convert the file to a GFileInputStream */ istream = G_INPUT_STREAM(g_file_read(priv->file, cancellable, error)); if (istream == NULL) return NULL; /* run the content type handlers until we get application/xml */ basename = g_file_get_basename(priv->file); file = priv->file; do { XbBuilderSourceAdapter *item; g_autofree gchar *content_type = NULL; g_autoptr(GInputStream) istream_tmp = NULL; g_autoptr(XbBuilderSourceCtx) ctx = xb_builder_source_ctx_new(file, istream); /* get the content type of the stream */ xb_builder_source_ctx_set_filename(ctx, basename); content_type = xb_builder_source_ctx_get_content_type(ctx, cancellable, error); g_debug("detected content type of %s to be %s", basename, content_type); if (content_type == NULL) return NULL; if (g_strcmp0(content_type, "application/xml") == 0) break; /* Also accept the text/xml alias, just in case the user’s content-type database is * slightly broken (application/xml should normally be what’s used): */ if (g_strcmp0(content_type, "text/xml") == 0) break; /* convert the stream */ item = xb_builder_source_get_adapter_by_mime(self, content_type); if (item == NULL || item->func_adapter == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot process content type %s", content_type); return NULL; } istream_tmp = item->func_adapter(self, ctx, item->user_data, cancellable, error); if (istream_tmp == NULL) return NULL; xb_builder_source_remove_last_extension(basename); g_set_object(&istream, istream_tmp); /* the #GFile is only useful for the outermost input stream, * for example it points to the .tar.gz file, while inner input * streams are the .xml output of decompressing the .gz in * memory and can’t be represented as a #GFile */ file = NULL; if (item->is_simple) break; } while (TRUE); return g_steal_pointer(&istream); } GFile * xb_builder_source_get_file(XbBuilderSource *self) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_SOURCE(self), NULL); return priv->file; } XbBuilderSourceFlags xb_builder_source_get_flags(XbBuilderSource *self) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_BUILDER_SOURCE(self), 0); return priv->flags; } static GInputStream * xb_builder_source_load_gzip_cb(XbBuilderSource *self, XbBuilderSourceCtx *ctx, gpointer user_data, GCancellable *cancellable, GError **error) { GInputStream *istream = xb_builder_source_ctx_get_stream(ctx); g_autoptr(GConverter) conv = NULL; conv = G_CONVERTER(g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP)); return g_converter_input_stream_new(istream, conv); } #ifdef HAVE_LZMA static GInputStream * xb_builder_source_load_lzma_cb(XbBuilderSource *self, XbBuilderSourceCtx *ctx, gpointer user_data, GCancellable *cancellable, GError **error) { GInputStream *istream = xb_builder_source_ctx_get_stream(ctx); g_autoptr(GConverter) conv = G_CONVERTER(xb_lzma_decompressor_new()); return g_converter_input_stream_new(istream, conv); } #endif #ifdef HAVE_ZSTD static GInputStream * xb_builder_source_load_zstd_cb(XbBuilderSource *self, XbBuilderSourceCtx *ctx, gpointer user_data, GCancellable *cancellable, GError **error) { GInputStream *istream = xb_builder_source_ctx_get_stream(ctx); g_autoptr(GConverter) conv = G_CONVERTER(xb_zstd_decompressor_new()); return g_converter_input_stream_new(istream, conv); } #endif static void xb_builder_source_adapter_free(XbBuilderSourceAdapter *item) { if (item->user_data_free != NULL) item->user_data_free(item->user_data); g_free(item->content_type); g_slice_free(XbBuilderSourceAdapter, item); } static void xb_builder_source_finalize(GObject *obj) { XbBuilderSource *self = XB_BUILDER_SOURCE(obj); XbBuilderSourcePrivate *priv = GET_PRIVATE(self); if (priv->istream != NULL) g_object_unref(priv->istream); if (priv->info != NULL) g_object_unref(priv->info); if (priv->file != NULL) g_object_unref(priv->file); g_ptr_array_unref(priv->fixups); g_ptr_array_unref(priv->adapters); g_free(priv->guid); g_free(priv->prefix); g_free(priv->content_type); G_OBJECT_CLASS(xb_builder_source_parent_class)->finalize(obj); } static void xb_builder_source_class_init(XbBuilderSourceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = xb_builder_source_finalize; } static void xb_builder_source_init(XbBuilderSource *self) { XbBuilderSourcePrivate *priv = GET_PRIVATE(self); priv->fixups = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->adapters = g_ptr_array_new_with_free_func((GDestroyNotify)xb_builder_source_adapter_free); xb_builder_source_add_adapter(self, "application/gzip,application/x-gzip,org.gnu.gnu-zip-archive", xb_builder_source_load_gzip_cb, NULL, NULL); #ifdef HAVE_LZMA xb_builder_source_add_adapter(self, "application/x-xz,org.tukaani.xz-archive", xb_builder_source_load_lzma_cb, NULL, NULL); #endif #ifdef HAVE_ZSTD xb_builder_source_add_adapter(self, "application/zstd", xb_builder_source_load_zstd_cb, NULL, NULL); #endif } /** * xb_builder_source_new: * * Creates a new builder source. * * Returns: a new #XbBuilderSource * * Since: 0.1.1 **/ XbBuilderSource * xb_builder_source_new(void) { return g_object_new(XB_TYPE_BUILDER_SOURCE, NULL); } libxmlb-0.3.22/src/xb-builder-source.h000066400000000000000000000062601476425255200175420ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-builder-fixup.h" #include "xb-builder-node.h" #include "xb-builder-source-ctx.h" #include "xb-compile.h" G_BEGIN_DECLS #define XB_TYPE_BUILDER_SOURCE (xb_builder_source_get_type()) G_DECLARE_DERIVABLE_TYPE(XbBuilderSource, xb_builder_source, XB, BUILDER_SOURCE, GObject) struct _XbBuilderSourceClass { GObjectClass parent_class; /*< private >*/ void (*_xb_reserved1)(void); void (*_xb_reserved2)(void); void (*_xb_reserved3)(void); void (*_xb_reserved4)(void); void (*_xb_reserved5)(void); void (*_xb_reserved6)(void); void (*_xb_reserved7)(void); }; /** * XbBuilderSourceFlags: * @XB_BUILDER_SOURCE_FLAG_NONE: No extra flags to use * @XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT: Do not attempt to repair XML whitespace * @XB_BUILDER_SOURCE_FLAG_WATCH_FILE: Watch the source file for changes * @XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY: Watch the directory containing the source file for *changes (for example, if watching all the sources in a directory — this allows the file monitors *to be shared) * * The flags for converting to XML. **/ typedef enum { XB_BUILDER_SOURCE_FLAG_NONE = 0, /* Since: 0.1.0 */ XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT = 1 << 0, /* Since: 0.1.0 */ XB_BUILDER_SOURCE_FLAG_WATCH_FILE = 1 << 1, /* Since: 0.1.0 */ XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY = 1 << 2, /* Since: 0.2.0 */ /*< private >*/ XB_BUILDER_SOURCE_FLAG_LAST } XbBuilderSourceFlags; typedef gboolean (*XbBuilderSourceNodeFunc)(XbBuilderSource *self, XbBuilderNode *bn, gpointer user_data, GError **error); typedef GInputStream *(*XbBuilderSourceAdapterFunc)(XbBuilderSource *self, XbBuilderSourceCtx *ctx, gpointer user_data, GCancellable *cancellable, GError **error); XbBuilderSource * xb_builder_source_new(void); gboolean xb_builder_source_load_file(XbBuilderSource *self, GFile *file, XbBuilderSourceFlags flags, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); gboolean xb_builder_source_load_xml(XbBuilderSource *self, const gchar *xml, XbBuilderSourceFlags flags, GError **error) G_GNUC_NON_NULL(1, 2); gboolean xb_builder_source_load_bytes(XbBuilderSource *self, GBytes *bytes, XbBuilderSourceFlags flags, GError **error) G_GNUC_NON_NULL(1, 2); void xb_builder_source_set_info(XbBuilderSource *self, XbBuilderNode *info) G_GNUC_NON_NULL(1); void xb_builder_source_set_prefix(XbBuilderSource *self, const gchar *prefix) G_GNUC_NON_NULL(1); void xb_builder_source_add_fixup(XbBuilderSource *self, XbBuilderFixup *fixup) G_GNUC_NON_NULL(1, 2); void xb_builder_source_add_adapter(XbBuilderSource *self, const gchar *content_types, XbBuilderSourceAdapterFunc func, gpointer user_data, GDestroyNotify user_data_free) G_GNUC_NON_NULL(1, 2, 3); void xb_builder_source_add_simple_adapter(XbBuilderSource *self, const gchar *content_types, XbBuilderSourceAdapterFunc func, gpointer user_data, GDestroyNotify user_data_free) G_GNUC_NON_NULL(1, 2, 3); G_END_DECLS libxmlb-0.3.22/src/xb-builder.c000066400000000000000000001032031476425255200162320ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbSilo" #include "config.h" #include #include #include "xb-builder-fixup-private.h" #include "xb-builder-node-private.h" #include "xb-builder-source-private.h" #include "xb-builder.h" #include "xb-opcode-private.h" #include "xb-silo-private.h" #include "xb-string-private.h" #include "xb-version.h" typedef struct { GPtrArray *sources; /* of XbBuilderSource */ GPtrArray *nodes; /* of XbBuilderNode */ GPtrArray *fixups; /* of XbBuilderFixup */ GPtrArray *locales; /* of str */ XbSiloProfileFlags profile_flags; GString *guid; } XbBuilderPrivate; G_DEFINE_TYPE_WITH_PRIVATE(XbBuilder, xb_builder, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_builder_get_instance_private(o)) typedef struct { XbSilo *silo; XbBuilderNode *root; /* transfer full */ XbBuilderNode *current; /* transfer none */ XbBuilderCompileFlags compile_flags; XbBuilderSourceFlags source_flags; GHashTable *strtab_hash; GByteArray *strtab; GPtrArray *locales; } XbBuilderCompileHelper; static guint32 xb_builder_compile_add_to_strtab(XbBuilderCompileHelper *helper, const gchar *str) { gpointer val; guint32 idx; /* already exists */ if (g_hash_table_lookup_extended(helper->strtab_hash, str, NULL, &val)) return GPOINTER_TO_UINT(val); /* new */ idx = helper->strtab->len; g_byte_array_append(helper->strtab, (const guint8 *)str, strlen(str) + 1); g_hash_table_insert(helper->strtab_hash, g_strdup(str), GUINT_TO_POINTER(idx)); return idx; } static gint xb_builder_get_locale_priority(XbBuilderCompileHelper *helper, const gchar *locale) { for (guint i = 0; i < helper->locales->len; i++) { const gchar *locale_tmp = g_ptr_array_index(helper->locales, i); if (g_strcmp0(locale_tmp, locale) == 0) return helper->locales->len - i; } return -1; } static void xb_builder_compile_start_element_cb(GMarkupParseContext *context, const gchar *element_name, const gchar **attr_names, const gchar **attr_values, gpointer user_data, GError **error) { XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; g_autoptr(XbBuilderNode) bn = xb_builder_node_new(element_name); /* parent node is being ignored */ if (helper->current != NULL && xb_builder_node_has_flag(helper->current, XB_BUILDER_NODE_FLAG_IGNORE)) xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE); /* check if we should ignore the locale */ if (!xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE) && helper->compile_flags & XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS) { const gchar *xml_lang = NULL; for (guint i = 0; attr_names[i] != NULL; i++) { if (g_strcmp0(attr_names[i], "xml:lang") == 0) { xml_lang = attr_values[i]; break; } } if (xml_lang == NULL) { if (helper->current != NULL) { gint prio = xb_builder_node_get_priority(helper->current); xb_builder_node_set_priority(bn, prio); } } else { gint prio = xb_builder_get_locale_priority(helper, xml_lang); if (prio < 0) xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE); xb_builder_node_set_priority(bn, prio); } } /* add attributes */ if (!xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) { for (guint i = 0; attr_names[i] != NULL; i++) xb_builder_node_set_attr(bn, attr_names[i], attr_values[i]); } /* add to tree */ xb_builder_node_add_child(helper->current, bn); helper->current = bn; } static void xb_builder_compile_end_element_cb(GMarkupParseContext *context, const gchar *element_name, gpointer user_data, GError **error) { XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; g_autoptr(XbBuilderNode) parent = xb_builder_node_get_parent(helper->current); if (parent == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Mismatched XML; no parent"); return; } helper->current = parent; } static void xb_builder_compile_text_cb(GMarkupParseContext *context, const gchar *text, gsize text_len, gpointer user_data, GError **error) { XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; XbBuilderNode *bn = helper->current; XbBuilderNode *bc = xb_builder_node_get_last_child(bn); /* unimportant */ if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) return; /* repair text unless we know it's valid */ if (helper->source_flags & XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT) xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_LITERAL_TEXT); /* text or tail */ if (!xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_HAS_TEXT)) { xb_builder_node_set_text(bn, text, text_len); return; } /* does this node have a child */ if (bc != NULL) { xb_builder_node_set_tail(bc, text, text_len); return; } /* always set a tail, even if already set */ xb_builder_node_set_tail(bn, text, text_len); } /** * xb_builder_import_source: * @self: a #XbSilo * @source: a #XbBuilderSource * * Adds a #XbBuilderSource to the #XbBuilder. * * Since: 0.1.0 **/ void xb_builder_import_source(XbBuilder *self, XbBuilderSource *source) { XbBuilderPrivate *priv = GET_PRIVATE(self); g_autofree gchar *guid = NULL; g_return_if_fail(XB_IS_BUILDER(self)); g_return_if_fail(XB_IS_BUILDER_SOURCE(source)); /* get latest GUID */ guid = xb_builder_source_get_guid(source); xb_builder_append_guid(self, guid); g_ptr_array_add(priv->sources, g_object_ref(source)); } static gboolean xb_builder_compile_source(XbBuilderCompileHelper *helper, XbBuilderSource *source, XbBuilderNode *root, GCancellable *cancellable, GError **error) { GPtrArray *children; XbBuilderNode *info; gsize chunk_size = 32 * 1024; gssize len; g_autofree gchar *data = NULL; g_autofree gchar *guid = xb_builder_source_get_guid(source); g_autoptr(GPtrArray) children_copy = NULL; g_autoptr(GInputStream) istream = NULL; g_autoptr(GMarkupParseContext) ctx = NULL; g_autoptr(GTimer) timer = xb_silo_start_profile(helper->silo); g_autoptr(XbBuilderNode) root_tmp = xb_builder_node_new(NULL); const GMarkupParser parser = {xb_builder_compile_start_element_cb, xb_builder_compile_end_element_cb, xb_builder_compile_text_cb, NULL, NULL}; /* add the source to a fake root in case it fails during processing */ helper->current = root_tmp; helper->source_flags = xb_builder_source_get_flags(source); /* decompress */ istream = xb_builder_source_get_istream(source, cancellable, error); if (istream == NULL) return FALSE; /* parse */ ctx = g_markup_parse_context_new(&parser, G_MARKUP_PREFIX_ERROR_POSITION, helper, NULL); data = g_malloc(chunk_size); while ((len = g_input_stream_read(istream, data, chunk_size, cancellable, error)) > 0) { if (!g_markup_parse_context_parse(ctx, data, len, error)) return FALSE; } if (len < 0) return FALSE; /* more opening than closing */ if (root_tmp != helper->current) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Mismatched XML"); return FALSE; } /* run any node functions */ if (!xb_builder_source_fixup(source, root_tmp, error)) return FALSE; /* check to see if the root was ignored */ if (xb_builder_node_has_flag(root_tmp, XB_BUILDER_NODE_FLAG_IGNORE)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "root node cannot be ignored"); return FALSE; } /* a single root with no siblings was required */ if (helper->compile_flags & XB_BUILDER_COMPILE_FLAG_SINGLE_ROOT) { if (xb_builder_node_get_children(root_tmp)->len > 1) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "A root node without siblings was required"); return FALSE; } } /* this is something we can query with later */ info = xb_builder_source_get_info(source); if (info != NULL) { children = xb_builder_node_get_children(helper->current); for (guint i = 0; i < children->len; i++) { XbBuilderNode *bn = g_ptr_array_index(children, i); if (!xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) xb_builder_node_add_child(bn, info); } } /* add the children to the main document */ children = xb_builder_node_get_children(root_tmp); children_copy = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < children->len; i++) { XbBuilderNode *bn = g_ptr_array_index(children, i); g_ptr_array_add(children_copy, g_object_ref(bn)); } for (guint i = 0; i < children_copy->len; i++) { XbBuilderNode *bn = g_ptr_array_index(children_copy, i); xb_builder_node_unlink(bn); xb_builder_node_add_child(root, bn); } /* success */ xb_silo_add_profile(helper->silo, timer, "compile %s", guid); return TRUE; } static gboolean xb_builder_strtab_element_names_cb(XbBuilderNode *bn, gpointer user_data) { XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; const gchar *tmp; /* root node */ if (xb_builder_node_get_element(bn) == NULL) return FALSE; if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) return FALSE; tmp = xb_builder_node_get_element(bn); xb_builder_node_set_element_idx(bn, xb_builder_compile_add_to_strtab(helper, tmp)); return FALSE; } static gboolean xb_builder_strtab_attr_name_cb(XbBuilderNode *bn, gpointer user_data) { GPtrArray *attrs; XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; /* root node */ if (xb_builder_node_get_element(bn) == NULL) return FALSE; if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) return FALSE; attrs = xb_builder_node_get_attrs(bn); for (guint i = 0; attrs != NULL && i < attrs->len; i++) { XbBuilderNodeAttr *attr = g_ptr_array_index(attrs, i); attr->name_idx = xb_builder_compile_add_to_strtab(helper, attr->name); } return FALSE; } static gboolean xb_builder_strtab_attr_value_cb(XbBuilderNode *bn, gpointer user_data) { GPtrArray *attrs; XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; /* root node */ if (xb_builder_node_get_element(bn) == NULL) return FALSE; if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) return FALSE; attrs = xb_builder_node_get_attrs(bn); for (guint i = 0; attrs != NULL && i < attrs->len; i++) { XbBuilderNodeAttr *attr = g_ptr_array_index(attrs, i); attr->value_idx = xb_builder_compile_add_to_strtab(helper, attr->value); } return FALSE; } static gboolean xb_builder_strtab_text_cb(XbBuilderNode *bn, gpointer user_data) { XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; const gchar *tmp; /* root node */ if (xb_builder_node_get_element(bn) == NULL) return FALSE; if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) return FALSE; if (xb_builder_node_get_text(bn) != NULL) { tmp = xb_builder_node_get_text(bn); xb_builder_node_set_text_idx(bn, xb_builder_compile_add_to_strtab(helper, tmp)); } if (xb_builder_node_get_tail(bn) != NULL) { tmp = xb_builder_node_get_tail(bn); xb_builder_node_set_tail_idx(bn, xb_builder_compile_add_to_strtab(helper, tmp)); } return FALSE; } static gboolean xb_builder_strtab_tokens_cb(XbBuilderNode *bn, gpointer user_data) { XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; GPtrArray *tokens = xb_builder_node_get_tokens(bn); /* root node */ if (xb_builder_node_get_element(bn) == NULL) return FALSE; if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) return FALSE; if (tokens == NULL) return FALSE; for (guint i = 0; i < MIN(tokens->len, XB_OPCODE_TOKEN_MAX); i++) { const gchar *tmp = g_ptr_array_index(tokens, i); if (tmp == NULL) continue; xb_builder_node_add_token_idx(bn, xb_builder_compile_add_to_strtab(helper, tmp)); } return FALSE; } static gboolean xb_builder_xml_lang_prio_cb(XbBuilderNode *bn, gpointer user_data) { GPtrArray *nodes_to_destroy = (GPtrArray *)user_data; gint prio_best = 0; g_autoptr(GPtrArray) nodes = g_ptr_array_new(); GPtrArray *siblings; g_autoptr(XbBuilderNode) parent = xb_builder_node_get_parent(bn); /* root node */ if (xb_builder_node_get_element(bn) == NULL) return FALSE; /* already ignored */ if (xb_builder_node_get_priority(bn) == -2) return FALSE; /* get all the siblings with the same name */ siblings = xb_builder_node_get_children(parent); for (guint i = 0; i < siblings->len; i++) { XbBuilderNode *bn2 = g_ptr_array_index(siblings, i); if (g_strcmp0(xb_builder_node_get_element(bn), xb_builder_node_get_element(bn2)) == 0) g_ptr_array_add(nodes, bn2); } /* only one thing, so bail early */ if (nodes->len == 1) return FALSE; /* find the best locale */ for (guint i = 0; i < nodes->len; i++) { XbBuilderNode *bn2 = g_ptr_array_index(nodes, i); if (xb_builder_node_get_priority(bn2) > prio_best) prio_best = xb_builder_node_get_priority(bn2); } /* add any nodes not as good as the bext locale to the kill list */ for (guint i = 0; i < nodes->len; i++) { XbBuilderNode *bn2 = g_ptr_array_index(nodes, i); if (xb_builder_node_get_priority(bn2) < prio_best) g_ptr_array_add(nodes_to_destroy, g_object_ref(bn2)); /* never visit this node again */ xb_builder_node_set_priority(bn2, -2); } return FALSE; } static gboolean xb_builder_nodetab_size_cb(XbBuilderNode *bn, gpointer user_data) { guint32 *sz = (guint32 *)user_data; /* root node */ if (xb_builder_node_get_element(bn) == NULL) return FALSE; if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) return FALSE; *sz += xb_builder_node_size(bn) + 1; /* +1 for the sentinel */ return FALSE; } typedef struct { GByteArray *buf; } XbBuilderNodetabHelper; static void xb_builder_nodetab_write_sentinel(XbBuilderNodetabHelper *helper) { XbSiloNode sn = { .flags = XB_SILO_NODE_FLAG_NONE, .attr_count = 0, }; // g_debug ("SENT @%u", (guint) helper->buf->len); g_byte_array_append(helper->buf, (const guint8 *)&sn, xb_silo_node_get_size(&sn)); } static void xb_builder_nodetab_write_node(XbBuilderNodetabHelper *helper, XbBuilderNode *bn) { GPtrArray *attrs = xb_builder_node_get_attrs(bn); GArray *token_idxs = xb_builder_node_get_token_idxs(bn); XbSiloNode sn = { .flags = XB_SILO_NODE_FLAG_IS_ELEMENT, .attr_count = (attrs != NULL) ? attrs->len : 0, .element_name = xb_builder_node_get_element_idx(bn), .next = 0x0, .parent = 0x0, .text = xb_builder_node_get_text_idx(bn), .tail = xb_builder_node_get_tail_idx(bn), .token_count = 0, }; /* add tokens */ if (token_idxs != NULL) sn.flags |= XB_SILO_NODE_FLAG_IS_TOKENIZED; /* if the node had no children and the text is just whitespace then * remove it even in literal mode */ if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_LITERAL_TEXT)) { if (xb_string_isspace(xb_builder_node_get_text(bn), -1)) sn.text = XB_SILO_UNSET; if (xb_string_isspace(xb_builder_node_get_tail(bn), -1)) sn.tail = XB_SILO_UNSET; } /* save this so we can set up the ->next pointers correctly */ xb_builder_node_set_offset(bn, helper->buf->len); // g_debug ("NODE @%u (%s)", (guint) helper->buf->len, xb_builder_node_get_element //(bn)); /* there is no point adding more tokens than we can match */ if (token_idxs != NULL) sn.token_count = MIN(token_idxs->len, XB_OPCODE_TOKEN_MAX); /* add to the buf */ g_byte_array_append(helper->buf, (const guint8 *)&sn, sizeof(sn)); /* add to the buf */ for (guint i = 0; attrs != NULL && i < attrs->len; i++) { XbBuilderNodeAttr *ba = g_ptr_array_index(attrs, i); XbSiloNodeAttr attr = { .attr_name = ba->name_idx, .attr_value = ba->value_idx, }; g_byte_array_append(helper->buf, (const guint8 *)&attr, sizeof(attr)); } /* add tokens */ for (guint i = 0; i < sn.token_count; i++) { guint32 idx = g_array_index(token_idxs, guint32, i); g_byte_array_append(helper->buf, (const guint8 *)&idx, sizeof(idx)); } } static void xb_builder_nodetab_write(XbBuilderNodetabHelper *helper, XbBuilderNode *bn) { GPtrArray *children; /* ignore this */ if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) return; /* element */ if (xb_builder_node_get_element(bn) != NULL) xb_builder_nodetab_write_node(helper, bn); /* children */ children = xb_builder_node_get_children(bn); for (guint i = 0; i < children->len; i++) { XbBuilderNode *bc = g_ptr_array_index(children, i); xb_builder_nodetab_write(helper, bc); } /* sentinel */ if (xb_builder_node_get_element(bn) != NULL) xb_builder_nodetab_write_sentinel(helper); } static XbSiloNode * xb_builder_get_node(GByteArray *str, guint32 off) { return (XbSiloNode *)(str->data + off); } static gboolean xb_builder_nodetab_fix_cb(XbBuilderNode *bn, gpointer user_data) { GPtrArray *siblings; XbBuilderNodetabHelper *helper = (XbBuilderNodetabHelper *)user_data; XbSiloNode *sn; gboolean found = FALSE; g_autoptr(XbBuilderNode) parent = xb_builder_node_get_parent(bn); /* root node */ if (xb_builder_node_get_element(bn) == NULL) return FALSE; if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) return FALSE; /* get the position in the buffer */ sn = xb_builder_get_node(helper->buf, xb_builder_node_get_offset(bn)); if (sn == NULL) return FALSE; /* set the parent if the node has one */ if (xb_builder_node_get_element(parent) != NULL) sn->parent = xb_builder_node_get_offset(parent); /* set ->next if the node has one */ siblings = xb_builder_node_get_children(parent); for (guint i = 0; i < siblings->len; i++) { XbBuilderNode *bn2 = g_ptr_array_index(siblings, i); if (bn2 == bn) { found = TRUE; continue; } if (!found) continue; if (!xb_builder_node_has_flag(bn2, XB_BUILDER_NODE_FLAG_IGNORE)) { sn->next = xb_builder_node_get_offset(bn2); break; } } return FALSE; } static void xb_builder_compile_helper_free(XbBuilderCompileHelper *helper) { g_hash_table_unref(helper->strtab_hash); g_byte_array_unref(helper->strtab); g_clear_object(&helper->silo); g_object_unref(helper->root); g_free(helper); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(XbBuilderCompileHelper, xb_builder_compile_helper_free) static gchar * xb_builder_generate_guid(XbBuilder *self) { XbBuilderPrivate *priv = GET_PRIVATE(self); XbGuid guid = {0x0}; if (priv->guid->len > 0) { xb_guid_compute_for_data(&guid, (const guint8 *)priv->guid->str, priv->guid->len); } return xb_guid_to_string(&guid); } /** * xb_builder_import_node: * @self: a #XbSilo * @bn: a #XbBuilderNode * * Adds a node tree to the builder. * * If you are manually adding dynamic data sourced from a non-static source then you MUST use * xb_builder_append_guid() with the appropriate GUID value, e.g. the file name and mtime. * * If no appropriate value is available, the caller can use something like: * * g_autofree gchar *tmp = xb_builder_node_export(bn, XB_NODE_EXPORT_FLAG_NONE, NULL); * xb_builder_append_guid(builder, tmp); * * Failure to include an appropriate GUID value would allow an out-of-data silo to be used. * * Since: 0.1.0 **/ void xb_builder_import_node(XbBuilder *self, XbBuilderNode *bn) { XbBuilderPrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER(self)); g_return_if_fail(XB_IS_BUILDER_NODE(bn)); g_ptr_array_add(priv->nodes, g_object_ref(bn)); } /** * xb_builder_add_locale: * @self: a #XbSilo * @locale: a locale, e.g. "en_US" * * Adds a locale to the builder. Locales added first will be prioritised over * locales added later. * * Since: 0.1.0 **/ void xb_builder_add_locale(XbBuilder *self, const gchar *locale) { XbBuilderPrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER(self)); g_return_if_fail(locale != NULL); if (g_str_has_suffix(locale, ".UTF-8")) return; for (guint i = 0; i < priv->locales->len; i++) { const gchar *locale_tmp = g_ptr_array_index(priv->locales, i); if (g_strcmp0(locale_tmp, locale) == 0) return; } g_ptr_array_add(priv->locales, g_strdup(locale)); /* if the user changes LANG, the blob is no longer valid */ xb_builder_append_guid(self, locale); } static gboolean xb_builder_watch_source(XbBuilder *self, XbBuilderSource *source, XbSilo *silo, GCancellable *cancellable, GError **error) { GFile *file = xb_builder_source_get_file(source); g_autoptr(GFile) watched_file = NULL; if (file == NULL) return TRUE; if ((xb_builder_source_get_flags(source) & (XB_BUILDER_SOURCE_FLAG_WATCH_FILE | XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY)) == 0) return TRUE; if (xb_builder_source_get_flags(source) & XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY) watched_file = g_file_get_parent(file); else watched_file = g_object_ref(file); return xb_silo_watch_file(silo, watched_file, cancellable, error); } static gboolean xb_builder_watch_sources(XbBuilder *self, XbSilo *silo, GCancellable *cancellable, GError **error) { XbBuilderPrivate *priv = GET_PRIVATE(self); for (guint i = 0; i < priv->sources->len; i++) { XbBuilderSource *source = g_ptr_array_index(priv->sources, i); if (!xb_builder_watch_source(self, source, silo, cancellable, error)) return FALSE; } return TRUE; } /** * xb_builder_compile: * @self: a #XbSilo * @flags: some #XbBuilderCompileFlags, e.g. %XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT * @cancellable: a #GCancellable, or %NULL * @error: the #GError, or %NULL * * Compiles a #XbSilo. * * Returns: (transfer full): a #XbSilo, or %NULL for error * * Since: 0.1.0 **/ XbSilo * xb_builder_compile(XbBuilder *self, XbBuilderCompileFlags flags, GCancellable *cancellable, GError **error) { XbBuilderPrivate *priv = GET_PRIVATE(self); guint32 nodetabsz = sizeof(XbSiloHeader); g_autoptr(GByteArray) buf = NULL; g_autoptr(GBytes) blob = NULL; XbSiloHeader *hdrptr; XbSiloHeader hdr = { .magic = XB_SILO_MAGIC_BYTES, .version = XB_SILO_VERSION, .strtab = 0, .strtab_ntags = 0, .padding = {0x0}, .guid = {0x0}, .filesz = 0x0, }; XbBuilderNodetabHelper nodetab_helper = { .buf = NULL, }; g_autoptr(GPtrArray) nodes_to_destroy = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_autoptr(GTimer) timer = NULL; g_autoptr(XbBuilderCompileHelper) helper = NULL; g_return_val_if_fail(XB_IS_BUILDER(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* this is inferred */ if (flags & XB_BUILDER_COMPILE_FLAG_SINGLE_LANG) flags |= XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS; /* the builder needs to know the locales */ if (priv->locales->len == 0 && (flags & XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "No locales set and using NATIVE_LANGS"); return NULL; } /* create helper used for compiling */ helper = g_new0(XbBuilderCompileHelper, 1); helper->compile_flags = flags; helper->root = xb_builder_node_new(NULL); helper->silo = xb_silo_new(); helper->locales = priv->locales; helper->strtab = g_byte_array_new(); helper->strtab_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); /* for profiling */ xb_silo_set_profile_flags(helper->silo, priv->profile_flags); timer = xb_silo_start_profile(helper->silo); /* build node tree */ for (guint i = 0; i < priv->sources->len; i++) { XbBuilderSource *source = g_ptr_array_index(priv->sources, i); const gchar *prefix = xb_builder_source_get_prefix(source); g_autofree gchar *source_guid = xb_builder_source_get_guid(source); g_autoptr(XbBuilderNode) root = NULL; g_autoptr(GError) error_local = NULL; /* find, or create the prefix */ if (prefix != NULL) { root = xb_builder_node_get_child(helper->root, prefix, NULL); if (root == NULL) root = xb_builder_node_insert(helper->root, prefix, NULL); } else { /* don't allow damaged XML files to ruin all the next ones */ root = g_object_ref(helper->root); } /* watch the source */ if (!xb_builder_watch_source(self, source, helper->silo, cancellable, error)) return NULL; if (priv->profile_flags & XB_SILO_PROFILE_FLAG_DEBUG) g_debug("compiling %s…", source_guid); if (!xb_builder_compile_source(helper, source, root, cancellable, &error_local)) { if (flags & XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID) { g_debug("ignoring invalid file %s: %s", source_guid, error_local->message); continue; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to compile %s: ", source_guid); return NULL; } } /* run any node functions */ for (guint i = 0; i < priv->fixups->len; i++) { XbBuilderFixup *fixup = g_ptr_array_index(priv->fixups, i); if (!xb_builder_fixup_node(fixup, helper->root, error)) return NULL; } /* only include the highest priority translation */ if (flags & XB_BUILDER_COMPILE_FLAG_SINGLE_LANG) { xb_builder_node_traverse(helper->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xb_builder_xml_lang_prio_cb, nodes_to_destroy); for (guint i = 0; i < nodes_to_destroy->len; i++) { XbBuilderNode *bn = g_ptr_array_index(nodes_to_destroy, i); xb_builder_node_unlink(bn); } xb_silo_add_profile(helper->silo, timer, "filter single-lang"); } /* add any manually build nodes */ for (guint i = 0; i < priv->nodes->len; i++) { XbBuilderNode *bn = g_ptr_array_index(priv->nodes, i); xb_builder_node_add_child(helper->root, bn); } /* get the size of the nodetab */ xb_builder_node_traverse(helper->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xb_builder_nodetab_size_cb, &nodetabsz); buf = g_byte_array_sized_new(nodetabsz); xb_silo_add_profile(helper->silo, timer, "get size nodetab"); /* add everything to the strtab */ xb_builder_node_traverse(helper->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xb_builder_strtab_element_names_cb, helper); hdr.strtab_ntags = g_hash_table_size(helper->strtab_hash); xb_silo_add_profile(helper->silo, timer, "adding strtab element"); xb_builder_node_traverse(helper->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xb_builder_strtab_attr_name_cb, helper); xb_silo_add_profile(helper->silo, timer, "adding strtab attr name"); xb_builder_node_traverse(helper->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xb_builder_strtab_attr_value_cb, helper); xb_silo_add_profile(helper->silo, timer, "adding strtab attr value"); xb_builder_node_traverse(helper->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xb_builder_strtab_text_cb, helper); xb_silo_add_profile(helper->silo, timer, "adding strtab text"); xb_builder_node_traverse(helper->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xb_builder_strtab_tokens_cb, helper); xb_silo_add_profile(helper->silo, timer, "adding strtab tokens"); /* add the initial header */ hdr.strtab = nodetabsz; if (priv->guid->len > 0) { XbGuid guid_tmp; xb_guid_compute_for_data(&guid_tmp, (const guint8 *)priv->guid->str, priv->guid->len); memcpy(&hdr.guid, &guid_tmp, sizeof(guid_tmp)); } g_byte_array_append(buf, (const guint8 *)&hdr, sizeof(hdr)); /* write nodes to the nodetab */ nodetab_helper.buf = buf; xb_builder_nodetab_write(&nodetab_helper, helper->root); xb_silo_add_profile(helper->silo, timer, "writing nodetab"); /* set all the ->next and ->parent offsets */ xb_builder_node_traverse(helper->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xb_builder_nodetab_fix_cb, &nodetab_helper); xb_silo_add_profile(helper->silo, timer, "fixing ->parent and ->next"); /* append the string table */ g_byte_array_append(buf, (const guint8 *)helper->strtab->data, helper->strtab->len); xb_silo_add_profile(helper->silo, timer, "appending strtab"); /* update the file size */ hdrptr = (XbSiloHeader *)buf->data; hdrptr->filesz = buf->len; /* create data */ blob = g_bytes_new(buf->data, buf->len); if (!xb_silo_load_from_bytes(helper->silo, blob, XB_SILO_LOAD_FLAG_NONE, error)) return NULL; /* success */ return g_steal_pointer(&helper->silo); } /** * xb_builder_ensure: * @self: a #XbSilo * @file: a #GFile * @flags: some #XbBuilderCompileFlags, e.g. %XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID * @cancellable: a #GCancellable, or %NULL * @error: the #GError, or %NULL * * Ensures @file is up to date, and returns a compiled #XbSilo. * * If @silo is being used by a query (e.g. in another thread) then all node * data is immediately invalid. * * The returned #XbSilo will use the thread-default main context at the time of * calling this function for its future signal emissions. * * Returns: (transfer full): a #XbSilo, or %NULL for error * * Since: 0.1.0 **/ XbSilo * xb_builder_ensure(XbBuilder *self, GFile *file, XbBuilderCompileFlags flags, GCancellable *cancellable, GError **error) { XbBuilderPrivate *priv = GET_PRIVATE(self); XbSiloLoadFlags load_flags = XB_SILO_LOAD_FLAG_NONE; g_autofree gchar *fn = NULL; g_autoptr(XbSilo) silo_tmp = xb_silo_new(); g_autoptr(XbSilo) silo = NULL; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(XB_IS_BUILDER(self), NULL); g_return_val_if_fail(G_IS_FILE(file), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* watch the blob, so propagate flags */ if (flags & XB_BUILDER_COMPILE_FLAG_WATCH_BLOB) { load_flags |= XB_SILO_LOAD_FLAG_WATCH_BLOB; if (!xb_silo_watch_file(silo_tmp, file, cancellable, error)) return NULL; } /* ensure all the sources are watched */ if (!xb_builder_watch_sources(self, silo_tmp, cancellable, error)) return NULL; /* profile new silo if needed */ xb_silo_set_profile_flags(silo_tmp, priv->profile_flags); /* load the file and peek at the GUIDs */ fn = g_file_get_path(file); g_debug("attempting to load %s", fn); if (!xb_silo_load_from_file(silo_tmp, file, XB_SILO_LOAD_FLAG_NONE, cancellable, &error_local)) { g_debug("failed to load silo: %s", error_local->message); } else { g_autofree gchar *guid = xb_builder_generate_guid(self); if (priv->profile_flags & XB_SILO_PROFILE_FLAG_DEBUG) g_debug("GUID string: %s", priv->guid->str); g_debug("file: %s, current:%s", xb_silo_get_guid(silo_tmp), guid); /* no compile required */ if (g_strcmp0(xb_silo_get_guid(silo_tmp), guid) == 0 || (flags & XB_BUILDER_COMPILE_FLAG_IGNORE_GUID) > 0) { g_debug("loading silo with existing file contents"); return g_steal_pointer(&silo_tmp); } } /* fallback to just creating a new file */ silo = xb_builder_compile(self, flags, cancellable, error); if (silo == NULL) return NULL; /* this might seem unnecessary, but windows cannot do _chsize() (introduced in GLib commit * https://gitlab.gnome.org/GNOME/glib/-/commit/3f705ffa1230757b910a06a705104d4b0fee2c05) * on a mmap'd file -- so manually tear that down before writing the new file */ g_clear_object(&silo_tmp); if (!xb_silo_save_to_file(silo, file, NULL, error)) return NULL; /* load from a file to re-mmap it */ if (!xb_silo_load_from_file(silo, file, load_flags, cancellable, error)) return NULL; /* ensure all the sources are watched on the reloaded silo */ if (!xb_builder_watch_sources(self, silo, cancellable, error)) return NULL; /* success */ return g_steal_pointer(&silo); } /** * xb_builder_append_guid: * @self: a #XbSilo * @guid: any text, typcically a filename or GUID * * Adds the GUID to the internal correctness hash. * * Since: 0.1.0 **/ void xb_builder_append_guid(XbBuilder *self, const gchar *guid) { XbBuilderPrivate *priv = GET_PRIVATE(self); if (priv->guid->len > 0) g_string_append(priv->guid, "&"); g_string_append(priv->guid, guid); } /** * xb_builder_set_profile_flags: * @self: a #XbBuilder * @profile_flags: some #XbSiloProfileFlags, e.g. %XB_SILO_PROFILE_FLAG_DEBUG * * Enables or disables the collection of profiling data. * * Since: 0.1.1 **/ void xb_builder_set_profile_flags(XbBuilder *self, XbSiloProfileFlags profile_flags) { XbBuilderPrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_BUILDER(self)); priv->profile_flags = profile_flags; } /** * xb_builder_add_fixup: * @self: a #XbBuilder * @fixup: a #XbBuilderFixup * * Adds a function that will get run on every #XbBuilderNode compile creates * for the silo. This is run after all the #XbBuilderSource fixups have been * run. * * Since: 0.1.3 **/ void xb_builder_add_fixup(XbBuilder *self, XbBuilderFixup *fixup) { XbBuilderPrivate *priv = GET_PRIVATE(self); g_autofree gchar *guid = NULL; g_return_if_fail(XB_IS_BUILDER(self)); g_return_if_fail(XB_IS_BUILDER_FIXUP(fixup)); /* append function IDs */ guid = xb_builder_fixup_get_guid(fixup); xb_builder_append_guid(self, guid); g_ptr_array_add(priv->fixups, g_object_ref(fixup)); } static void xb_builder_finalize(GObject *obj) { XbBuilder *self = XB_BUILDER(obj); XbBuilderPrivate *priv = GET_PRIVATE(self); g_ptr_array_unref(priv->sources); g_ptr_array_unref(priv->nodes); g_ptr_array_unref(priv->locales); g_ptr_array_unref(priv->fixups); g_string_free(priv->guid, TRUE); G_OBJECT_CLASS(xb_builder_parent_class)->finalize(obj); } static void xb_builder_class_init(XbBuilderClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = xb_builder_finalize; } static void xb_builder_init(XbBuilder *self) { XbBuilderPrivate *priv = GET_PRIVATE(self); priv->sources = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->nodes = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->fixups = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->locales = g_ptr_array_new_with_free_func(g_free); priv->guid = g_string_new(xb_version_string()); } /** * xb_builder_new: * * Creates a new builder. * * The #XbSilo returned by the methods of this #XbBuilder will use the * thread-default main context at the time of calling this function for its * future signal emissions. * * Returns: a new #XbBuilder * * Since: 0.1.0 **/ XbBuilder * xb_builder_new(void) { return g_object_new(XB_TYPE_BUILDER, NULL); } libxmlb-0.3.22/src/xb-builder.h000066400000000000000000000052301476425255200162400ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-builder-fixup.h" #include "xb-builder-node.h" #include "xb-builder-source.h" #include "xb-compile.h" #include "xb-silo.h" G_BEGIN_DECLS #define XB_TYPE_BUILDER (xb_builder_get_type()) G_DECLARE_DERIVABLE_TYPE(XbBuilder, xb_builder, XB, BUILDER, GObject) struct _XbBuilderClass { GObjectClass parent_class; /*< private >*/ void (*_xb_reserved1)(void); void (*_xb_reserved2)(void); void (*_xb_reserved3)(void); void (*_xb_reserved4)(void); void (*_xb_reserved5)(void); void (*_xb_reserved6)(void); void (*_xb_reserved7)(void); }; /** * XbBuilderCompileFlags: * @XB_BUILDER_COMPILE_FLAG_NONE: No extra flags to use * @XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS: Only load native languages * @XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID: Ignore invalid files without an error * @XB_BUILDER_COMPILE_FLAG_SINGLE_LANG: Only store a single language * @XB_BUILDER_COMPILE_FLAG_WATCH_BLOB: Watch the XMLB file for changes * @XB_BUILDER_COMPILE_FLAG_IGNORE_GUID: Ignore the cache GUID value * @XB_BUILDER_COMPILE_FLAG_SINGLE_ROOT: Require at most one root node * * The flags for converting to XML. **/ typedef enum { XB_BUILDER_COMPILE_FLAG_NONE = 0, /* Since: 0.1.0 */ XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS = 1 << 1, /* Since: 0.1.0 */ XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID = 1 << 2, /* Since: 0.1.0 */ XB_BUILDER_COMPILE_FLAG_SINGLE_LANG = 1 << 3, /* Since: 0.1.0 */ XB_BUILDER_COMPILE_FLAG_WATCH_BLOB = 1 << 4, /* Since: 0.1.0 */ XB_BUILDER_COMPILE_FLAG_IGNORE_GUID = 1 << 5, /* Since: 0.1.7 */ XB_BUILDER_COMPILE_FLAG_SINGLE_ROOT = 1 << 6, /* Since: 0.3.4 */ /*< private >*/ XB_BUILDER_COMPILE_FLAG_LAST } XbBuilderCompileFlags; XbBuilder * xb_builder_new(void); void xb_builder_append_guid(XbBuilder *self, const gchar *guid) G_GNUC_NON_NULL(1, 2); void xb_builder_import_source(XbBuilder *self, XbBuilderSource *source) G_GNUC_NON_NULL(1, 2); void xb_builder_import_node(XbBuilder *self, XbBuilderNode *bn) G_GNUC_NON_NULL(1, 2); XbSilo * xb_builder_compile(XbBuilder *self, XbBuilderCompileFlags flags, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1); XbSilo * xb_builder_ensure(XbBuilder *self, GFile *file, XbBuilderCompileFlags flags, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); void xb_builder_add_locale(XbBuilder *self, const gchar *locale) G_GNUC_NON_NULL(1, 2); void xb_builder_add_fixup(XbBuilder *self, XbBuilderFixup *fixup) G_GNUC_NON_NULL(1, 2); void xb_builder_set_profile_flags(XbBuilder *self, XbSiloProfileFlags profile_flags) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-common-private.h000066400000000000000000000006231476425255200175530ustar00rootroot00000000000000/* * Copyright 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-compile.h" gchar * xb_content_type_guess(const gchar *filename, const guchar *buf, gsize bufsz); gboolean xb_file_set_contents(GFile *file, const guint8 *buf, gsize bufsz, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1); libxmlb-0.3.22/src/xb-common.c000066400000000000000000000075221476425255200161030ustar00rootroot00000000000000/* * Copyright 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbCommon" #include "config.h" #include #include "xb-common-private.h" static const gchar * xb_content_type_guess_from_fn(const gchar *filename) { gchar *ext; /* no ownership */ g_return_val_if_fail(filename != NULL, NULL); /* get file extension with dot */ ext = g_strrstr(filename, "."); if (ext == NULL) return NULL; /* map Windows "mime-type" to a content type */ if (g_strcmp0(ext, ".gz") == 0) return "application/gzip"; if (g_strcmp0(ext, ".xz") == 0) return "application/x-xz"; if (g_strcmp0(ext, ".zst") == 0) return "application/zstd"; if (g_strcmp0(ext, ".xml") == 0) return "application/xml"; if (g_strcmp0(ext, ".desktop") == 0) return "application/x-desktop"; if (g_strcmp0(ext, ".quirk") == 0) return "text/plain"; return NULL; } static gboolean xb_content_type_match(const guchar *buf, gsize bufsz, gsize offset, const gchar *magic, gsize magic_size) { /* document too small */ if (offset + magic_size > bufsz) return FALSE; return memcmp(buf + offset, magic, magic_size) == 0; } /** * xb_content_type_guess: (skip) * @filename: (nullable): filename * @buf: (nullable): file data buffer * @bufsz: size of file data buffer * * Guesses the content type based on example data. Either @filename or @buf may * be %NULL, in which case the guess will be based solely on the other argument. * * Returns: a string indicating a guessed content type **/ gchar * xb_content_type_guess(const gchar *filename, const guchar *buf, gsize bufsz) { g_autofree gchar *content_type = NULL; /* check for bad results, e.g. from Chrome OS */ content_type = g_content_type_guess(filename, buf, bufsz, NULL); if (g_strstr_len(content_type, -1, "/") == NULL || g_strcmp0(content_type, "application/octet-stream") == 0 || g_strcmp0(content_type, "text/plain") == 0) { /* magic */ if (bufsz > 0) { if (xb_content_type_match(buf, bufsz, 0x0, "\x1f\x8b", 2)) return g_strdup("application/gzip"); if (xb_content_type_match(buf, bufsz, 0x0, "\xfd\x37\x7a\x58\x5a\x00", 6)) return g_strdup("application/x-xz"); if (xb_content_type_match(buf, bufsz, 0x0, "\x28\xb5\x2f\xfd", 4)) return g_strdup("application/zstd"); if (xb_content_type_match(buf, bufsz, 0x0, " * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include /* see https://bugzilla.gnome.org/show_bug.cgi?id=113075 */ #ifndef G_GNUC_NON_NULL #if !defined(_WIN32) && (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3) #define G_GNUC_NON_NULL(params...) __attribute__((nonnull(params))) #else #define G_GNUC_NON_NULL(params...) #endif #endif libxmlb-0.3.22/src/xb-lzma-decompressor.c000066400000000000000000000112341476425255200202540ustar00rootroot00000000000000/* * Copyright 2009 Red Hat, Inc. * Copyright 2009 Shaun McCance * Copyright 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include #include #include #include #include "xb-lzma-decompressor.h" static void xb_lzma_decompressor_iface_init(GConverterIface *iface); struct _XbLzmaDecompressor { GObject parent_instance; lzma_stream lzmastream; }; G_DEFINE_TYPE_WITH_CODE(XbLzmaDecompressor, xb_lzma_decompressor, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE(G_TYPE_CONVERTER, xb_lzma_decompressor_iface_init)) static void xb_lzma_decompressor_finalize(GObject *object) { XbLzmaDecompressor *self = XB_LZMA_DECOMPRESSOR(object); lzma_end(&self->lzmastream); G_OBJECT_CLASS(xb_lzma_decompressor_parent_class)->finalize(object); } static void xb_lzma_decompressor_init(XbLzmaDecompressor *self) { } static void xb_lzma_decompressor_constructed(GObject *object) { XbLzmaDecompressor *self = XB_LZMA_DECOMPRESSOR(object); lzma_stream tmp = LZMA_STREAM_INIT; lzma_ret res; self->lzmastream = tmp; res = lzma_auto_decoder(&self->lzmastream, SIZE_MAX, 0); if (res == LZMA_MEM_ERROR) g_error("XbLzmaDecompressor: Not enough memory for lzma use"); if (res == LZMA_OPTIONS_ERROR) g_error("XbLzmaDecompressor: Unsupported flags"); if (res != LZMA_OK) g_error("XbLzmaDecompressor: Unexpected lzma error"); } static void xb_lzma_decompressor_class_init(XbLzmaDecompressorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = xb_lzma_decompressor_finalize; object_class->constructed = xb_lzma_decompressor_constructed; } XbLzmaDecompressor * xb_lzma_decompressor_new(void) { return g_object_new(XB_TYPE_LZMA_DECOMPRESSOR, NULL); } static void xb_lzma_decompressor_reset(GConverter *converter) { XbLzmaDecompressor *self = XB_LZMA_DECOMPRESSOR(converter); lzma_ret res; /* lzma doesn't have a reset function. Ending and reiniting * might do the trick. But this is untested. If reset matters * to you, test this. */ lzma_end(&self->lzmastream); res = lzma_code(&self->lzmastream, LZMA_RUN); if (res == LZMA_MEM_ERROR) g_error("XbLzmaDecompressor: Not enough memory for lzma use"); if (res != LZMA_OK) g_error("XbLzmaDecompressor: Unexpected lzma error"); } static GConverterResult xb_lzma_decompressor_convert(GConverter *converter, const void *inbuf, gsize inbuf_size, void *outbuf, gsize outbuf_size, GConverterFlags flags, gsize *bytes_read, gsize *bytes_written, GError **error) { XbLzmaDecompressor *self = XB_LZMA_DECOMPRESSOR(converter); lzma_ret res; self->lzmastream.next_in = (void *)inbuf; self->lzmastream.avail_in = inbuf_size; self->lzmastream.next_out = outbuf; self->lzmastream.avail_out = outbuf_size; res = lzma_code(&self->lzmastream, LZMA_RUN); if (res == LZMA_DATA_ERROR) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid compressed data"); return G_CONVERTER_ERROR; } if (res == LZMA_UNSUPPORTED_CHECK) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Cannot calculate the integrity check"); return G_CONVERTER_ERROR; } if (res == LZMA_MEM_ERROR) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Not enough memory"); return G_CONVERTER_ERROR; } if (res == LZMA_FORMAT_ERROR) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "File format not recognized"); return G_CONVERTER_ERROR; } if (res == LZMA_OPTIONS_ERROR) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Invalid or unsupported options"); return G_CONVERTER_ERROR; } if (res == LZMA_BUF_ERROR) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "No progress is possible"); return G_CONVERTER_ERROR; } if (res == LZMA_PROG_ERROR) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Programming error"); return G_CONVERTER_ERROR; } if (res == LZMA_OK || res == LZMA_STREAM_END) { *bytes_read = inbuf_size - self->lzmastream.avail_in; *bytes_written = outbuf_size - self->lzmastream.avail_out; if (res == LZMA_STREAM_END) return G_CONVERTER_FINISHED; return G_CONVERTER_CONVERTED; } /* fallback */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unhandled error code %u", res); return G_CONVERTER_ERROR; } static void xb_lzma_decompressor_iface_init(GConverterIface *iface) { iface->convert = xb_lzma_decompressor_convert; iface->reset = xb_lzma_decompressor_reset; } libxmlb-0.3.22/src/xb-lzma-decompressor.h000066400000000000000000000007401476425255200202610ustar00rootroot00000000000000/* * Copyright 2009 Red Hat, Inc. * Copyright 2009 Shaun McCance * Copyright 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include G_BEGIN_DECLS #define XB_TYPE_LZMA_DECOMPRESSOR (xb_lzma_decompressor_get_type()) G_DECLARE_FINAL_TYPE(XbLzmaDecompressor, xb_lzma_decompressor, XB, LZMA_DECOMPRESSOR, GObject) XbLzmaDecompressor * xb_lzma_decompressor_new(void); G_END_DECLS libxmlb-0.3.22/src/xb-machine-private.h000066400000000000000000000006671476425255200176770ustar00rootroot00000000000000/* * Copyright 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-machine.h" G_BEGIN_DECLS gboolean xb_machine_stack_pop_two(XbMachine *self, XbStack *stack, XbOpcode *opcode1_out, XbOpcode *opcode2_out, GError **error) G_GNUC_NON_NULL(1, 2, 3, 4); void xb_machine_opcode_tokenize(XbMachine *self, XbOpcode *op) G_GNUC_NON_NULL(1, 2); G_END_DECLS libxmlb-0.3.22/src/xb-machine.c000066400000000000000000002030311476425255200162100ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbMachine" #include "config.h" #include #include #if !GLIB_CHECK_VERSION(2, 54, 0) #include #endif #include "xb-machine-private.h" #include "xb-opcode-private.h" #include "xb-silo-private.h" #include "xb-stack-private.h" #include "xb-string-private.h" typedef struct { XbMachineDebugFlags debug_flags; GPtrArray *methods; /* of XbMachineMethodItem */ GPtrArray *operators; /* of XbMachineOperator */ GPtrArray *text_handlers; /* of XbMachineTextHandlerItem */ GHashTable *opcode_fixup; /* of str[XbMachineOpcodeFixupItem] */ GHashTable *opcode_tokens; /* of utf8 */ guint stack_size; } XbMachinePrivate; G_DEFINE_TYPE_WITH_PRIVATE(XbMachine, xb_machine, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_machine_get_instance_private(o)) typedef struct { gchar *str; gsize strsz; gchar *name; } XbMachineOperator; typedef struct { XbMachineOpcodeFixupFunc fixup_cb; gpointer user_data; GDestroyNotify user_data_free; } XbMachineOpcodeFixupItem; typedef struct { XbMachineTextHandlerFunc handler_cb; gpointer user_data; GDestroyNotify user_data_free; } XbMachineTextHandlerItem; typedef struct { guint32 idx; gchar *name; guint n_opcodes; XbMachineMethodFunc method_cb; gpointer user_data; GDestroyNotify user_data_free; } XbMachineMethodItem; #define XB_MACHINE_STACK_LEVELS_MAX 20 /** * xb_machine_set_debug_flags: * @self: a #XbMachine * @flags: #XbMachineDebugFlags, e.g. %XB_MACHINE_DEBUG_FLAG_SHOW_STACK * * Sets the debug level of the virtual machine. * * Since: 0.1.1 **/ void xb_machine_set_debug_flags(XbMachine *self, XbMachineDebugFlags flags) { XbMachinePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_MACHINE(self)); priv->debug_flags = flags; } /** * xb_machine_add_operator: * @self: a #XbMachine * @str: operator string, e.g. `==` * @name: function name, e.g. `contains` * * Adds a new operator to the virtual machine. Operators can then be used * instead of explicit methods like `eq()`. * * You need to add a custom operator using xb_machine_add_operator() before * using xb_machine_parse(). Common operators like `<=` and `=` are built-in * and do not have to be added manually. * * Since: 0.1.1 **/ void xb_machine_add_operator(XbMachine *self, const gchar *str, const gchar *name) { XbMachineOperator *op; XbMachinePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_MACHINE(self)); g_return_if_fail(str != NULL); g_return_if_fail(name != NULL); op = g_slice_new0(XbMachineOperator); op->str = g_strdup(str); op->strsz = strlen(str); op->name = g_strdup(name); g_ptr_array_add(priv->operators, op); } /** * xb_machine_add_method: * @self: a #XbMachine * @name: function name, e.g. `contains` * @n_opcodes: minimum number of opcodes required on the stack * @method_cb: function to call * @user_data: user pointer to pass to @method_cb, or %NULL * @user_data_free: a function which gets called to free @user_data, or %NULL * * Adds a new function to the virtual machine. Registered functions can then be * used as methods. * * The @method_cb must not modify the stack it’s passed unless it’s going to * succeed. In particular, if a method call is not optimisable, it must not * modify the stack it’s passed. * * You need to add a custom function using xb_machine_add_method() before using * methods that may reference it, for example xb_machine_add_opcode_fixup(). * * Since: 0.1.1 **/ void xb_machine_add_method(XbMachine *self, const gchar *name, guint n_opcodes, XbMachineMethodFunc method_cb, gpointer user_data, GDestroyNotify user_data_free) { XbMachineMethodItem *item; XbMachinePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_MACHINE(self)); g_return_if_fail(name != NULL); g_return_if_fail(method_cb != NULL); item = g_slice_new0(XbMachineMethodItem); item->idx = priv->methods->len; item->name = g_strdup(name); item->n_opcodes = n_opcodes; item->method_cb = method_cb; item->user_data = user_data; item->user_data_free = user_data_free; g_ptr_array_add(priv->methods, item); } /** * xb_machine_add_opcode_fixup: * @self: a #XbMachine * @opcodes_sig: signature, e.g. `INTE,TEXT` * @fixup_cb: callback * @user_data: user pointer to pass to @fixup_cb * @user_data_free: a function which gets called to free @user_data, or %NULL * * Adds an opcode fixup. Fixups can be used to optimize the stack of opcodes or * to add support for a nonstandard feature, for instance supporting missing * attributes to functions. * * Since: 0.1.1 **/ void xb_machine_add_opcode_fixup(XbMachine *self, const gchar *opcodes_sig, XbMachineOpcodeFixupFunc fixup_cb, gpointer user_data, GDestroyNotify user_data_free) { XbMachineOpcodeFixupItem *item = g_slice_new0(XbMachineOpcodeFixupItem); XbMachinePrivate *priv = GET_PRIVATE(self); item->fixup_cb = fixup_cb; item->user_data = user_data; item->user_data_free = user_data_free; g_hash_table_insert(priv->opcode_fixup, g_strdup(opcodes_sig), item); } /** * xb_machine_add_text_handler: * @self: a #XbMachine * @handler_cb: callback * @user_data: user pointer to pass to @handler_cb * @user_data_free: a function which gets called to free @user_data, or %NULL * * Adds a text handler. This allows the virtual machine to support nonstandard * encoding or shorthand mnemonics for standard functions. * * Since: 0.1.1 **/ void xb_machine_add_text_handler(XbMachine *self, XbMachineTextHandlerFunc handler_cb, gpointer user_data, GDestroyNotify user_data_free) { XbMachineTextHandlerItem *item = g_slice_new0(XbMachineTextHandlerItem); XbMachinePrivate *priv = GET_PRIVATE(self); item->handler_cb = handler_cb; item->user_data = user_data; item->user_data_free = user_data_free; g_ptr_array_add(priv->text_handlers, item); } static XbMachineMethodItem * xb_machine_find_func(XbMachine *self, const gchar *func_name) { XbMachinePrivate *priv = GET_PRIVATE(self); for (guint i = 0; i < priv->methods->len; i++) { XbMachineMethodItem *item = g_ptr_array_index(priv->methods, i); if (g_strcmp0(item->name, func_name) == 0) return item; } return NULL; } /** * xb_machine_opcode_func_init: * @self: a #XbMachine * @opcode: (out caller-allocates): a stack allocated #XbOpcode to initialise * @func_name: function name, e.g. `eq` * * Initialises a stack allocated #XbOpcode for a registered function. * Some standard functions are registered by default, for instance `eq` or `ge`. * Other functions have to be added using xb_machine_add_method(). * * Returns: %TRUE if the function was found and the opcode initialised, %FALSE * otherwise * Since: 0.2.0 **/ gboolean xb_machine_opcode_func_init(XbMachine *self, XbOpcode *opcode, const gchar *func_name) { XbMachineMethodItem *item = xb_machine_find_func(self, func_name); if (item == NULL) return FALSE; xb_opcode_init(opcode, XB_OPCODE_KIND_FUNCTION, g_strdup(func_name), item->idx, g_free); return TRUE; } static gboolean xb_machine_parse_add_func(XbMachine *self, XbStack *opcodes, const gchar *func_name, guint8 level, GError **error) { XbOpcode *opcode; if (!xb_stack_push(opcodes, &opcode, error)) return FALSE; /* match opcode, which should always exist */ if (!xb_machine_opcode_func_init(self, opcode, func_name)) { if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "built-in function not found: %s", func_name); } xb_stack_pop(opcodes, NULL, NULL); return FALSE; } xb_opcode_set_level(opcode, level); return TRUE; } #if !GLIB_CHECK_VERSION(2, 54, 0) static gboolean str_has_sign(const gchar *str) { return str[0] == '-' || str[0] == '+'; } static gboolean str_has_hex_prefix(const gchar *str) { return str[0] == '0' && g_ascii_tolower(str[1]) == 'x'; } static gboolean g_ascii_string_to_unsigned(const gchar *str, guint base, guint64 min, guint64 max, guint64 *out_num, GError **error) { const gchar *end_ptr = NULL; gint saved_errno = 0; guint64 number; g_return_val_if_fail(str != NULL, FALSE); g_return_val_if_fail(base >= 2 && base <= 36, FALSE); g_return_val_if_fail(min <= max, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (str[0] == '\0') { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Empty string is not a number"); return FALSE; } errno = 0; number = g_ascii_strtoull(str, (gchar **)&end_ptr, base); saved_errno = errno; if (g_ascii_isspace(str[0]) || str_has_sign(str) || (base == 16 && str_has_hex_prefix(str)) || (saved_errno != 0 && saved_errno != ERANGE) || end_ptr == NULL || *end_ptr != '\0') { if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "“%s” is not an unsigned number", str); } return FALSE; } if (saved_errno == ERANGE || number < min || number > max) { if (error != NULL) { g_autofree gchar *min_str = g_strdup_printf("%" G_GUINT64_FORMAT, min); g_autofree gchar *max_str = g_strdup_printf("%" G_GUINT64_FORMAT, max); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Number “%s” is out of bounds [%s, %s]", str, min_str, max_str); } return FALSE; } if (out_num != NULL) *out_num = number; return TRUE; } #endif static gboolean xb_machine_parse_add_text(XbMachine *self, XbStack *opcodes, const gchar *text, gssize text_len, guint8 level, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); g_autofree gchar *str = NULL; guint64 val; /* NULL is perfectly valid */ if (text == NULL) { XbOpcode *opcode; if (!xb_stack_push(opcodes, &opcode, error)) return FALSE; xb_opcode_text_init_static(opcode, str); return TRUE; } /* never add empty literals */ if (text_len < 0) text_len = strlen(text); if (text_len == 0) return TRUE; /* do any additional handlers */ str = g_strndup(text, text_len); for (guint i = 0; i < priv->text_handlers->len; i++) { XbMachineTextHandlerItem *item = g_ptr_array_index(priv->text_handlers, i); gboolean handled = FALSE; guint opcodes_sz = xb_stack_get_size(opcodes); if (!item->handler_cb(self, opcodes, str, &handled, item->user_data, error)) return FALSE; if (handled) { /* ideally the XbMachineTextHandlerFunc would contain a `guint8 level` but * that is now public ABI. Just fixup the level for any added opcodes */ for (guint j = xb_stack_get_size(opcodes); j > opcodes_sz; j--) { XbOpcode *op_tmp = xb_stack_peek(opcodes, j - 1); xb_opcode_set_level(op_tmp, level); } return TRUE; } } /* quoted text */ if (text_len >= 2) { if (str[0] == '\'' && str[text_len - 1] == '\'') { g_autofree gchar *tmp = g_strndup(str + 1, text_len - 2); XbOpcode *opcode; if (!xb_stack_push(opcodes, &opcode, error)) return FALSE; xb_opcode_text_init_steal(opcode, g_steal_pointer(&tmp)); xb_opcode_set_level(opcode, level); return TRUE; } } /* indexed text */ if (text_len >= 3) { if (str[0] == '$' && str[1] == '\'' && str[text_len - 1] == '\'') { g_autofree gchar *tmp = g_strndup(str + 2, text_len - 3); XbOpcode *opcode; if (!xb_stack_push(opcodes, &opcode, error)) return FALSE; xb_opcode_init(opcode, XB_OPCODE_KIND_INDEXED_TEXT, g_steal_pointer(&tmp), XB_SILO_UNSET, g_free); xb_opcode_set_level(opcode, level); return TRUE; } } /* bind variables */ if (g_strcmp0(str, "?") == 0) { XbOpcode *opcode; if (!xb_stack_push(opcodes, &opcode, error)) return FALSE; xb_opcode_bind_init(opcode); xb_opcode_set_level(opcode, level); return TRUE; } /* check for plain integer */ if (g_ascii_string_to_unsigned(str, 10, 0, G_MAXUINT32, &val, NULL)) { XbOpcode *opcode; if (!xb_stack_push(opcodes, &opcode, error)) return FALSE; xb_opcode_integer_init(opcode, val); xb_opcode_set_level(opcode, level); return TRUE; } /* not supported */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot parse text or number `%s`", str); } return FALSE; } static gboolean xb_machine_parse_section(XbMachine *self, XbStack *opcodes, const gchar *text, gssize text_len, gboolean is_method, guint8 level, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); /* fall back for simplicity */ if (text_len < 0) text_len = strlen(text); if (text_len == 0) return TRUE; for (gssize i = 0; i < text_len; i++) { for (guint j = 0; j < priv->operators->len; j++) { XbMachineOperator *op = g_ptr_array_index(priv->operators, j); if (strncmp(text + i, op->str, op->strsz) != 0) continue; if (is_method) { XbOpcode *op_tail; const gchar *op_name = op->name; /* after then before */ if (!xb_machine_parse_section(self, opcodes, text + i + op->strsz, -1, is_method, level, error)) return FALSE; if (i > 0) { if (!xb_machine_parse_section(self, opcodes, text, i, FALSE, level, error)) return FALSE; } /* multiple "eq" sections are converted to "in" */ op_tail = xb_stack_peek_tail(opcodes); if (op_tail != NULL && _xb_opcode_get_level(op_tail) != level && g_strcmp0(op_name, "eq") == 0) op_name = "in"; if (!xb_machine_parse_add_func(self, opcodes, op_name, level, error)) return FALSE; } else { /* before then after */ if (i > 0) { if (!xb_machine_parse_section(self, opcodes, text, i, FALSE, level, error)) return FALSE; } if (!xb_machine_parse_section(self, opcodes, text + i + op->strsz, -1, is_method, level, error)) return FALSE; if (!xb_machine_parse_add_func(self, opcodes, op->name, level, error)) return FALSE; } return TRUE; } } /* nothing matched */ if (is_method) { g_autoptr(GError) error_local = NULL; if (!xb_machine_parse_add_text(self, opcodes, text, text_len, level, &error_local)) { if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_PARSING) g_debug("Failed to add text %s, trying function", text); return xb_machine_parse_add_func(self, opcodes, text, level, error); } return TRUE; } return xb_machine_parse_add_text(self, opcodes, text, text_len, level, error); } static gboolean xb_machine_parse_sections(XbMachine *self, XbStack *opcodes, const gchar *text, gsize text_len, gboolean is_method, guint8 level, GError **error) { g_autofree gchar *tmp = NULL; if (text_len == 0) return TRUE; /* leading comma */ if (text[0] == ',') { tmp = g_strndup(text + 1, text_len - 1); } else { tmp = g_strndup(text, text_len); } for (gint i = text_len - 1; i >= 0; i--) { if (tmp[i] == ',') { tmp[i] = '\0'; if (is_method) { if (!xb_machine_parse_add_func(self, opcodes, tmp + i + 1, level, error)) return FALSE; is_method = FALSE; } else { if (!xb_machine_parse_section(self, opcodes, tmp + i + 1, -1, TRUE, level, error)) return FALSE; } } } if (tmp[0] != '\0') { if (!xb_machine_parse_section(self, opcodes, tmp, -1, is_method, level, error)) return FALSE; } return TRUE; } static gchar * xb_machine_get_opcodes_sig(XbMachine *self, XbStack *opcodes) { GString *str = g_string_new(NULL); for (guint i = 0; i < xb_stack_get_size(opcodes); i++) { XbOpcode *op = xb_stack_peek(opcodes, i); g_autofree gchar *sig = xb_opcode_get_sig(op); g_string_append_printf(str, "%s,", sig); } if (str->len > 0) g_string_truncate(str, str->len - 1); return g_string_free(str, FALSE); } /* @results *must* have enough space * @op is transfer full into this function */ static gboolean xb_machine_opcodes_optimize_fn(XbMachine *self, XbStack *opcodes, XbOpcode op, XbStack *results, GError **error) { XbMachineMethodItem *item; XbMachinePrivate *priv = GET_PRIVATE(self); g_autofree gchar *stack_str = NULL; g_autoptr(GError) error_local = NULL; g_auto(XbOpcode) op_result = XB_OPCODE_INIT(); g_auto(XbOpcode) op_owned = op; /* not a function */ if (_xb_opcode_get_kind(&op) != XB_OPCODE_KIND_FUNCTION) { XbOpcode *op_out; if (!xb_stack_push(results, &op_out, error)) return FALSE; *op_out = xb_opcode_steal(&op_owned); return TRUE; } /* get function, check if we have enough arguments */ item = g_ptr_array_index(priv->methods, _xb_opcode_get_val(&op)); if (item->n_opcodes > xb_stack_get_size(opcodes)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "predicate invalid -- not enough args"); return FALSE; } /* run the method. it's only supposed to pop its arguments off the stack * if it can complete successfully */ if (!item->method_cb(self, opcodes, NULL, item->user_data, NULL, &error_local)) { XbOpcode *op_out; if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { stack_str = xb_stack_to_string(opcodes); g_debug("ignoring optimized call to %s(%s): %s", item->name, stack_str, error_local->message); } if (!xb_stack_push(results, &op_out, error)) return FALSE; *op_out = xb_opcode_steal(&op_owned); return TRUE; } /* the method ran, add the result. the arguments have already been popped */ if (!xb_machine_stack_pop(self, opcodes, &op_result, error)) return FALSE; if (_xb_opcode_get_kind(&op_result) != XB_OPCODE_KIND_BOOLEAN || _xb_opcode_get_val(&op_result)) { XbOpcode *op_out; if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { g_autofree gchar *tmp = xb_opcode_to_string(&op_result); g_debug("method ran, adding result %s", tmp); } if (!xb_stack_push(results, &op_out, error)) return FALSE; xb_opcode_set_level(&op_result, _xb_opcode_get_level(&op)); *op_out = xb_opcode_steal(&op_result); return TRUE; } /* the predicate will always evalulate to FALSE */ if (error != NULL) { stack_str = xb_stack_to_string(opcodes); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "the predicate will always evalulate to FALSE: %s", stack_str); } return FALSE; } static gboolean xb_machine_opcodes_optimize(XbMachine *self, XbStack *opcodes, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); g_autoptr(XbStack) results = xb_stack_new_inline(xb_stack_get_size(opcodes)); g_auto(XbOpcode) op = XB_OPCODE_INIT(); /* debug */ if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { g_autofree gchar *str = xb_stack_to_string(opcodes); g_debug("before optimizing: %s", str); } /* process the stack in reverse order */ while (xb_machine_stack_pop(self, opcodes, &op, NULL)) { /* this takes ownership of @op */ if (!xb_machine_opcodes_optimize_fn(self, opcodes, xb_opcode_steal(&op), results, error)) return FALSE; } /* copy back the result into the opcodes stack (and reverse it) */ while (xb_stack_pop(results, &op, NULL)) { XbOpcode *op_out; if (!xb_stack_push(opcodes, &op_out, error)) return FALSE; *op_out = xb_opcode_steal(&op); } /* debug */ if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { g_autofree gchar *str = xb_stack_to_string(opcodes); g_debug("after optimizing: %s", str); } return TRUE; } static gsize xb_machine_parse_text(XbMachine *self, XbStack *opcodes, const gchar *text, gsize text_len, guint8 level, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); guint tail = 0; /* sanity check */ if (level > XB_MACHINE_STACK_LEVELS_MAX) { if (error != NULL) { g_autofree gchar *tmp = g_strndup(text, text_len); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "nesting deeper than 20 levels supported: %s", tmp); } return G_MAXSIZE; } for (guint i = 0; i < text_len; i++) { if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_PARSING) g_debug("LVL %u\t%u:\t\t%c", level, i, text[i]); if (text[i] == '(') { gsize j = 0; j = xb_machine_parse_text(self, opcodes, text + i + 1, text_len - i, level + 1, error); if (j == G_MAXSIZE) return G_MAXSIZE; if (!xb_machine_parse_sections(self, opcodes, text + tail, i - tail, TRUE, level, error)) return G_MAXSIZE; i += j; tail = i + 1; continue; } if (text[i] == ')') { if (!xb_machine_parse_sections(self, opcodes, text + tail, i - tail, FALSE, level, error)) return G_MAXSIZE; return i + 1; } } if (tail != text_len && level > 0) { if (error != NULL) { g_autofree gchar *tmp = g_strndup(text, text_len); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "brackets did not match: %s", tmp); } return G_MAXSIZE; } if (!xb_machine_parse_sections(self, opcodes, text + tail, text_len - tail, FALSE, level, error)) return G_MAXSIZE; return 0; } /** * xb_machine_parse_full: * @self: a #XbMachine * @text: predicate to parse, e.g. `contains(text(),'xyx')` * @text_len: length of @text, or -1 if @text is `NUL` terminated * @flags: #XbMachineParseFlags, e.g. %XB_MACHINE_PARSE_FLAG_OPTIMIZE * @error: a #GError, or %NULL * * Parses an XPath predicate. Not all of XPath 1.0 or XPath 1.0 is supported, * and new functions and mnemonics can be added using xb_machine_add_method() * and xb_machine_add_text_handler(). * * Returns: (transfer full): opcodes, or %NULL on error * * Since: 0.1.4 **/ XbStack * xb_machine_parse_full(XbMachine *self, const gchar *text, gssize text_len, XbMachineParseFlags flags, GError **error) { XbMachineOpcodeFixupItem *item; XbMachinePrivate *priv = GET_PRIVATE(self); guint8 level = 0; g_autoptr(XbStack) opcodes = NULL; g_autofree gchar *opcodes_sig = NULL; g_return_val_if_fail(XB_IS_MACHINE(self), NULL); g_return_val_if_fail(text != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* assume NUL terminated */ if (text_len < 0) text_len = strlen(text); if (text_len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "string was zero size"); return NULL; } /* parse into opcodes */ opcodes = xb_stack_new(priv->stack_size); if (xb_machine_parse_text(self, opcodes, text, text_len, level, error) == G_MAXSIZE) return NULL; /* do any fixups */ opcodes_sig = xb_machine_get_opcodes_sig(self, opcodes); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) g_debug("opcodes_sig=%s", opcodes_sig); item = g_hash_table_lookup(priv->opcode_fixup, opcodes_sig); if (item != NULL) { if (!item->fixup_cb(self, opcodes, item->user_data, error)) return NULL; } /* optimize */ if (flags & XB_MACHINE_PARSE_FLAG_OPTIMIZE) { for (guint i = 0; i < 10; i++) { guint oldsz = xb_stack_get_size(opcodes); /* Is the stack optimal already? */ if (oldsz == 1) break; if (!xb_machine_opcodes_optimize(self, opcodes, error)) return NULL; if (oldsz == xb_stack_get_size(opcodes)) break; } } /* success */ return g_steal_pointer(&opcodes); } /** * xb_machine_parse: * @self: a #XbMachine * @text: predicate to parse, e.g. `contains(text(),'xyx')` * @text_len: length of @text, or -1 if @text is `NUL` terminated * @error: a #GError, or %NULL * * Parses an XPath predicate. Not all of XPath 1.0 or XPath 1.0 is supported, * and new functions and mnemonics can be added using xb_machine_add_method() * and xb_machine_add_text_handler(). * * Returns: (transfer full): opcodes, or %NULL on error * * Since: 0.1.1 **/ XbStack * xb_machine_parse(XbMachine *self, const gchar *text, gssize text_len, GError **error) { return xb_machine_parse_full(self, text, text_len, XB_MACHINE_PARSE_FLAG_OPTIMIZE, error); } static void xb_machine_debug_show_stack(XbMachine *self, XbStack *stack) { g_autofree gchar *str = NULL; if (xb_stack_get_size(stack) == 0) { g_debug("stack is empty"); return; } str = xb_stack_to_string(stack); g_debug("stack: %s", str); } static gboolean xb_machine_run_func(XbMachine *self, XbStack *stack, XbOpcode *opcode, gpointer exec_data, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); XbMachineMethodItem *item = g_ptr_array_index(priv->methods, _xb_opcode_get_val(opcode)); /* optional debugging */ if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) { g_autofree gchar *str = xb_opcode_to_string(opcode); g_debug("running: %s", str); xb_machine_debug_show_stack(self, stack); } /* check we have enough stack elements */ if (item->n_opcodes > xb_stack_get_size(stack)) { if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "function required %u arguments, stack only has %u", item->n_opcodes, xb_stack_get_size(stack)); } return FALSE; } if (!item->method_cb(self, stack, NULL, item->user_data, exec_data, error)) { g_prefix_error(error, "failed to call %s(): ", item->name); return FALSE; } return TRUE; } /** * xb_machine_run: * @self: a #XbMachine * @opcodes: a #XbStack of opcodes * @result: (out): return status after running @opcodes * @exec_data: per-run user data that is passed to all the #XbMachineMethodFunc functions * @error: a #GError, or %NULL * * Runs a set of opcodes on the virtual machine. * * It is safe to call this function from a different thread to the one that * created the #XbMachine. * * Returns: a new #XbOpcode, or %NULL * * Since: 0.1.1 * Deprecated: 0.3.0: Use xb_machine_run_with_bindings() instead. **/ gboolean xb_machine_run(XbMachine *self, XbStack *opcodes, gboolean *result, gpointer exec_data, GError **error) { return xb_machine_run_with_bindings(self, opcodes, NULL, result, exec_data, error); } /** * xb_machine_run_with_bindings: * @self: a #XbMachine * @opcodes: a #XbStack of opcodes * @bindings: (nullable) (transfer none): values bound to opcodes of type * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if * the query doesn’t need any bound values * @result: (out): return status after running @opcodes * @exec_data: per-run user data that is passed to all the #XbMachineMethodFunc functions * @error: a #GError, or %NULL * * Runs a set of opcodes on the virtual machine, using the bound values given in * @bindings to substitute for bound opcodes. * * It is safe to call this function from a different thread to the one that * created the #XbMachine. * * Returns: a new #XbOpcode, or %NULL * * Since: 0.3.0 **/ gboolean xb_machine_run_with_bindings(XbMachine *self, XbStack *opcodes, XbValueBindings *bindings, gboolean *result, gpointer exec_data, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); g_auto(XbOpcode) opcode_success = XB_OPCODE_INIT(); g_autoptr(XbStack) stack = NULL; guint opcodes_stack_size = xb_stack_get_size(opcodes); guint bound_opcode_idx = 0; g_return_val_if_fail(XB_IS_MACHINE(self), FALSE); g_return_val_if_fail(opcodes != NULL, FALSE); g_return_val_if_fail(result != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* process each opcode */ stack = xb_stack_new_inline(priv->stack_size); for (guint i = 0; i < opcodes_stack_size; i++) { XbOpcode *opcode = xb_stack_peek(opcodes, i); XbOpcodeKind kind = _xb_opcode_get_kind(opcode); /* replace post-0.3.0-style bound opcodes with their bound values */ if (bindings != NULL && (kind == XB_OPCODE_KIND_BOUND_TEXT || kind == XB_OPCODE_KIND_BOUND_INDEXED_TEXT || kind == XB_OPCODE_KIND_BOUND_INTEGER)) { XbOpcode *machine_opcode; if (!xb_machine_stack_push(self, stack, &machine_opcode, error)) return FALSE; if (!xb_value_bindings_lookup_opcode(bindings, bound_opcode_idx++, machine_opcode)) { if (error != NULL) { g_autofree gchar *tmp1 = xb_stack_to_string(stack); g_autofree gchar *tmp2 = xb_stack_to_string(opcodes); g_set_error( error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "opcode was not bound at runtime, stack:%s, opcodes:%s", tmp1, tmp2); } return FALSE; } continue; } if (kind == XB_OPCODE_KIND_BOUND_UNSET) { if (error != NULL) { g_autofree gchar *tmp1 = xb_stack_to_string(stack); g_autofree gchar *tmp2 = xb_stack_to_string(opcodes); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "opcode was not bound at runtime, stack:%s, opcodes:%s", tmp1, tmp2); } return FALSE; } /* process the stack */ if (kind == XB_OPCODE_KIND_FUNCTION) { if (!xb_machine_run_func(self, stack, opcode, exec_data, error)) return FALSE; continue; } /* add to stack; this uses a const copy of the input opcode, * so ownership of anything allocated on the heap remains with * the caller */ if (kind == XB_OPCODE_KIND_TEXT || kind == XB_OPCODE_KIND_BOOLEAN || kind == XB_OPCODE_KIND_INTEGER || kind == XB_OPCODE_KIND_INDEXED_TEXT || (bindings == NULL && (kind == XB_OPCODE_KIND_BOUND_TEXT || kind == XB_OPCODE_KIND_BOUND_INDEXED_TEXT || kind == XB_OPCODE_KIND_BOUND_INTEGER))) { XbOpcode *machine_opcode; if (!xb_machine_stack_push(self, stack, &machine_opcode, error)) return FALSE; *machine_opcode = *opcode; machine_opcode->destroy_func = NULL; continue; } /* invalid */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "opcode kind %u not recognised", kind); } return FALSE; } /* the stack should have one boolean left on the stack */ if (xb_stack_get_size(stack) != 1) { if (error != NULL) { g_autofree gchar *tmp = xb_stack_to_string(stack); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%u opcodes remain on the stack (%s)", xb_stack_get_size(stack), tmp); } return FALSE; } if (!xb_stack_pop(stack, &opcode_success, error)) return FALSE; if (_xb_opcode_get_kind(&opcode_success) != XB_OPCODE_KIND_BOOLEAN) { if (error != NULL) { g_autofree gchar *tmp = xb_stack_to_string(stack); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Expected boolean, got: %s", tmp); } return FALSE; } *result = _xb_opcode_get_val(&opcode_success); /* success */ return TRUE; } /** * xb_machine_stack_pop: * @self: a #XbMachine * @stack: a #XbStack * @opcode_out: (out caller-allocates) (optional): return location for the popped #XbOpcode * @error: a #GError, or %NULL * * Pops an opcode from the stack. * * Returns: %TRUE if popping succeeded, %FALSE if the stack was empty already * * Since: 0.2.0 **/ gboolean xb_machine_stack_pop(XbMachine *self, XbStack *stack, XbOpcode *opcode_out, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); gboolean retval; if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) { XbOpcode *opcode_peek = xb_stack_peek(stack, xb_stack_get_size(stack) - 1); if (opcode_peek != NULL) { g_autofree gchar *str = xb_opcode_to_string(opcode_peek); g_debug("popping: %s", str); } else { g_debug("not popping: stack empty"); } } retval = xb_stack_pop(stack, opcode_out, error); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack(self, stack); return retval; } /** * xb_machine_stack_pop_two: (skip): **/ gboolean xb_machine_stack_pop_two(XbMachine *self, XbStack *stack, XbOpcode *opcode1_out, XbOpcode *opcode2_out, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); gboolean retval; if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) { XbOpcode *opcode_peek1 = xb_stack_peek(stack, xb_stack_get_size(stack) - 1); XbOpcode *opcode_peek2 = xb_stack_peek(stack, xb_stack_get_size(stack) - 2); if (opcode_peek1 != NULL && opcode_peek2 != NULL) { g_autofree gchar *str1 = xb_opcode_to_string(opcode_peek1); g_autofree gchar *str2 = xb_opcode_to_string(opcode_peek2); g_debug("popping1: %s", str1); g_debug("popping2: %s", str2); } else { g_debug("not popping: stack empty"); } } retval = xb_stack_pop_two(stack, opcode1_out, opcode2_out, error); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack(self, stack); return retval; } /** * xb_machine_stack_push: * @self: a #XbMachine * @stack: a #XbStack * @opcode_out: (out) (nullable): return location for the new #XbOpcode * @error: return location for a #GError, or %NULL * * Pushes a new empty opcode onto the end of the stack. A pointer to the opcode * is returned in @opcode_out so that the caller can initialise it. * * If the stack reaches its maximum size, %G_IO_ERROR_NO_SPACE will be returned. * * Returns: %TRUE if a new empty opcode was returned, or %FALSE if the stack has * reached its maximum size * Since: 0.2.0 **/ gboolean xb_machine_stack_push(XbMachine *self, XbStack *stack, XbOpcode **opcode_out, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); if (G_UNLIKELY(priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK)) { g_debug("pushing generic opcode"); } return xb_stack_push(stack, opcode_out, error); } /** * xb_machine_stack_push_text: * @self: a #XbMachine * @stack: a #XbStack * @str: text literal * @error: return location for a #GError, or %NULL * * Adds a text literal to the stack, copying @str. * * Errors are as for xb_machine_stack_push(). * * Returns: %TRUE on success, %FALSE otherwise * Since: 0.2.0 **/ gboolean xb_machine_stack_push_text(XbMachine *self, XbStack *stack, const gchar *str, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); XbOpcode *opcode; if (G_UNLIKELY(priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK)) g_debug("pushing: %s", str); if (!xb_stack_push(stack, &opcode, error)) return FALSE; xb_opcode_text_init(opcode, str); if (G_UNLIKELY(priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK)) xb_machine_debug_show_stack(self, stack); return TRUE; } /** * xb_machine_stack_push_text_static: * @self: a #XbMachine * @stack: a #XbStack * @str: text literal * @error: return location for a #GError, or %NULL * * Adds static text literal to the stack. * * Errors are as for xb_machine_stack_push(). * * Returns: %TRUE on success, %FALSE otherwise * Since: 0.2.0 **/ gboolean xb_machine_stack_push_text_static(XbMachine *self, XbStack *stack, const gchar *str, GError **error) { XbOpcode *opcode; XbMachinePrivate *priv = GET_PRIVATE(self); if (G_UNLIKELY(priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK)) g_debug("pushing: %s", str); if (!xb_stack_push(stack, &opcode, error)) return FALSE; xb_opcode_text_init_static(opcode, str); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack(self, stack); return TRUE; } /** * xb_machine_stack_push_text_steal: * @self: a #XbMachine * @stack: a #XbStack * @str: (transfer full): text literal * @error: return location for a #GError, or %NULL * * Adds a stolen text literal to the stack. * * Errors are as for xb_machine_stack_push(). * * Returns: %TRUE on success, %FALSE otherwise * Since: 0.2.0 **/ gboolean xb_machine_stack_push_text_steal(XbMachine *self, XbStack *stack, gchar *str, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); XbOpcode *opcode; g_autofree gchar *str_stolen = g_steal_pointer(&str); if (G_UNLIKELY(priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK)) g_debug("pushing: %s", str_stolen); if (!xb_stack_push(stack, &opcode, error)) return FALSE; xb_opcode_text_init_steal(opcode, g_steal_pointer(&str_stolen)); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack(self, stack); return TRUE; } /** * xb_machine_stack_push_integer: * @self: a #XbMachine * @stack: a #XbStack * @val: integer literal * @error: return location for a #GError, or %NULL * * Adds an integer literal to the stack. * * Errors are as for xb_machine_stack_push(). * * Returns: %TRUE on success, %FALSE otherwise * Since: 0.2.0 **/ gboolean xb_machine_stack_push_integer(XbMachine *self, XbStack *stack, guint32 val, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); XbOpcode *opcode; if (G_UNLIKELY(priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK)) g_debug("pushing: %u", val); if (!xb_stack_push(stack, &opcode, error)) return FALSE; xb_opcode_integer_init(opcode, val); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack(self, stack); return TRUE; } /** * xb_machine_set_stack_size: * @self: a #XbMachine * @stack_size: integer * * Sets the maximum stack size used for the machine. * * The stack size will be affective for new jobs started with xb_machine_run() * and xb_machine_parse(). * * Since: 0.1.3 **/ void xb_machine_set_stack_size(XbMachine *self, guint stack_size) { XbMachinePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_MACHINE(self)); g_return_if_fail(stack_size != 0); priv->stack_size = stack_size; } /** * xb_machine_get_stack_size: * @self: a #XbMachine * * Gets the maximum stack size used for the machine. * * Returns: integer * * Since: 0.1.3 **/ guint xb_machine_get_stack_size(XbMachine *self) { XbMachinePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_MACHINE(self), 0); return priv->stack_size; } static const gchar * xb_machine_intern_token(XbMachine *self, const gchar *str) { XbMachinePrivate *priv = GET_PRIVATE(self); const gchar *tmp; gchar *newstr; /* existing value */ tmp = g_hash_table_lookup(priv->opcode_tokens, str); if (tmp != NULL) return tmp; /* add as both key and value */ newstr = g_strdup(str); g_hash_table_add(priv->opcode_tokens, newstr); return newstr; } /* private */ void xb_machine_opcode_tokenize(XbMachine *self, XbOpcode *op) { const gchar *str; g_auto(GStrv) tokens = NULL; g_auto(GStrv) ascii_tokens = NULL; /* use the fast token path even if there are no valid tokens */ xb_opcode_add_flag(op, XB_OPCODE_FLAG_TOKENIZED); str = _xb_opcode_get_str(op); tokens = g_str_tokenize_and_fold(str, NULL, &ascii_tokens); for (guint i = 0; tokens[i] != NULL; i++) { if (!xb_string_token_valid(tokens[i])) continue; xb_opcode_append_token(op, xb_machine_intern_token(self, tokens[i])); } for (guint i = 0; ascii_tokens[i] != NULL; i++) { if (!xb_string_token_valid(ascii_tokens[i])) continue; xb_opcode_append_token(op, xb_machine_intern_token(self, ascii_tokens[i])); } } typedef gboolean (*OpcodeCheckFunc)(XbOpcode *op); static gboolean _xb_opcode_cmp_val_or_str(XbOpcode *op) { return xb_opcode_cmp_str(op) || _xb_opcode_cmp_int(op) || _xb_opcode_cmp_itx(op); } static gboolean xb_machine_check_one_arg(XbStack *stack, OpcodeCheckFunc f, GError **error) { XbOpcode *head; head = xb_stack_peek_tail(stack); if (head == NULL || !f(head)) { if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s type not supported", (head != NULL) ? xb_opcode_kind_to_string(_xb_opcode_get_kind(head)) : "(null)"); } return FALSE; } return TRUE; } static gboolean xb_machine_check_two_args(XbStack *stack, OpcodeCheckFunc f1, OpcodeCheckFunc f2, GError **error) { XbOpcode *head1 = NULL; XbOpcode *head2 = NULL; guint stack_size = xb_stack_get_size(stack); if (stack_size >= 2) { head1 = xb_stack_peek(stack, stack_size - 1); head2 = xb_stack_peek(stack, stack_size - 2); } if (head1 == NULL || head2 == NULL || !f1(head1) || !f2(head2)) { if (error != NULL) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s:%s types not supported", (head1 != NULL) ? xb_opcode_kind_to_string(_xb_opcode_get_kind(head1)) : "(null)", (head2 != NULL) ? xb_opcode_kind_to_string(_xb_opcode_get_kind(head2)) : "(null)"); } return FALSE; } return TRUE; } static gboolean xb_machine_func_and_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_int, _xb_opcode_cmp_int, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ return xb_stack_push_bool(stack, _xb_opcode_get_val(&op1) && _xb_opcode_get_val(&op2), error); } static gboolean xb_machine_func_or_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_int, _xb_opcode_cmp_int, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ return xb_stack_push_bool(stack, _xb_opcode_get_val(&op1) || _xb_opcode_get_val(&op2), error); } static gboolean xb_machine_func_eq_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_val_or_str, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ if ((_xb_opcode_cmp_int(&op1) && _xb_opcode_cmp_int(&op2)) || (_xb_opcode_cmp_itx(&op1) && _xb_opcode_cmp_itx(&op2))) return xb_stack_push_bool(stack, _xb_opcode_get_val(&op1) == _xb_opcode_get_val(&op2), error); /* TEXT:TEXT */ if (xb_opcode_cmp_str(&op1) && xb_opcode_cmp_str(&op2)) { if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_SLOW_PATH) { g_autofree gchar *str1 = xb_opcode_to_string(&op1); g_autofree gchar *str2 = xb_opcode_to_string(&op2); g_debug("slow strcmp fallback of %s:%s", str1, str2); } return xb_stack_push_bool( stack, g_strcmp0(_xb_opcode_get_str(&op1), _xb_opcode_get_str(&op2)) == 0, error); } /* INTE:TEXT */ if (_xb_opcode_cmp_int(&op1) && xb_opcode_cmp_str(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_SLOW_PATH) { g_autofree gchar *str1 = xb_opcode_to_string(&op1); g_autofree gchar *str2 = xb_opcode_to_string(&op2); g_debug("slow atoi fallback of %s:%s", str1, str2); } if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val == _xb_opcode_get_val(&op1), error); } /* TEXT:INTE */ if (xb_opcode_cmp_str(&op1) && _xb_opcode_cmp_int(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op1) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_SLOW_PATH) { g_autofree gchar *str1 = xb_opcode_to_string(&op1); g_autofree gchar *str2 = xb_opcode_to_string(&op2); g_debug("slow atoi fallback of %s:%s", str1, str2); } if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val == _xb_opcode_get_val(&op2), error); } /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot compare %s and %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op1)), xb_opcode_kind_to_string(_xb_opcode_get_kind(&op2))); } return FALSE; } static gboolean xb_machine_func_ne_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_val_or_str, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ if ((_xb_opcode_cmp_int(&op1) && _xb_opcode_cmp_int(&op2)) || (_xb_opcode_cmp_itx(&op1) && _xb_opcode_cmp_itx(&op2))) { return xb_stack_push_bool(stack, _xb_opcode_get_val(&op1) != _xb_opcode_get_val(&op2), error); } /* TEXT:TEXT */ if (xb_opcode_cmp_str(&op1) && xb_opcode_cmp_str(&op2)) { return xb_stack_push_bool( stack, g_strcmp0(_xb_opcode_get_str(&op1), _xb_opcode_get_str(&op2)) != 0, error); } /* INTE:TEXT */ if (_xb_opcode_cmp_int(&op1) && xb_opcode_cmp_str(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val != _xb_opcode_get_val(&op1), error); } /* TEXT:INTE */ if (xb_opcode_cmp_str(&op1) && _xb_opcode_cmp_int(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op1) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val != _xb_opcode_get_val(&op2), error); } /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot compare %s and %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op1)), xb_opcode_kind_to_string(_xb_opcode_get_kind(&op2))); } return FALSE; } static gboolean xb_machine_func_lt_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_val_or_str, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ if (_xb_opcode_cmp_int(&op1) && _xb_opcode_cmp_int(&op2)) { return xb_stack_push_bool(stack, _xb_opcode_get_val(&op2) < _xb_opcode_get_val(&op1), error); } /* TEXT:TEXT */ if (xb_opcode_cmp_str(&op1) && xb_opcode_cmp_str(&op2)) { return xb_stack_push_bool( stack, g_strcmp0(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)) < 0, error); } /* INTE:TEXT */ if (_xb_opcode_cmp_int(&op1) && xb_opcode_cmp_str(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val < _xb_opcode_get_val(&op1), error); } /* TEXT:INTE */ if (xb_opcode_cmp_str(&op1) && _xb_opcode_cmp_int(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op1) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val < _xb_opcode_get_val(&op2), error); } /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot compare %s and %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op1)), xb_opcode_kind_to_string(_xb_opcode_get_kind(&op2))); } return FALSE; } static gboolean xb_machine_func_gt_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_val_or_str, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ if (_xb_opcode_cmp_int(&op1) && _xb_opcode_cmp_int(&op2)) { return xb_stack_push_bool(stack, _xb_opcode_get_val(&op2) > _xb_opcode_get_val(&op1), error); } /* TEXT:TEXT */ if (xb_opcode_cmp_str(&op1) && xb_opcode_cmp_str(&op2)) { return xb_stack_push_bool( stack, g_strcmp0(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)) > 0, error); } /* INTE:TEXT */ if (_xb_opcode_cmp_int(&op1) && xb_opcode_cmp_str(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val > _xb_opcode_get_val(&op1), error); } /* TEXT:INTE */ if (xb_opcode_cmp_str(&op1) && _xb_opcode_cmp_int(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op1) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val > _xb_opcode_get_val(&op2), error); } /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot compare %s and %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op1)), xb_opcode_kind_to_string(_xb_opcode_get_kind(&op2))); } return FALSE; } static gboolean xb_machine_func_le_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_val_or_str, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ if (_xb_opcode_cmp_int(&op1) && _xb_opcode_cmp_int(&op2)) { return xb_stack_push_bool(stack, _xb_opcode_get_val(&op2) <= _xb_opcode_get_val(&op1), error); } /* TEXT:TEXT */ if (xb_opcode_cmp_str(&op1) && xb_opcode_cmp_str(&op2)) { return xb_stack_push_bool( stack, g_strcmp0(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)) <= 0, error); return TRUE; } /* INTE:TEXT */ if (_xb_opcode_cmp_int(&op1) && xb_opcode_cmp_str(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val <= _xb_opcode_get_val(&op1), error); } /* TEXT:INTE */ if (xb_opcode_cmp_str(&op1) && _xb_opcode_cmp_int(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op1) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val <= _xb_opcode_get_val(&op2), error); } /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot compare %s and %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op1)), xb_opcode_kind_to_string(_xb_opcode_get_kind(&op2))); } return FALSE; } static gboolean xb_machine_func_lower_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op = XB_OPCODE_INIT(); if (!xb_machine_check_one_arg(stack, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* TEXT */ return xb_machine_stack_push_text_steal(self, stack, g_utf8_strdown(_xb_opcode_get_str(&op), -1), error); } static gboolean xb_machine_func_upper_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op = XB_OPCODE_INIT(); if (!xb_machine_check_one_arg(stack, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* TEXT */ return xb_machine_stack_push_text_steal(self, stack, g_utf8_strup(_xb_opcode_get_str(&op), -1), error); } static gboolean xb_machine_func_not_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op = XB_OPCODE_INIT(); if (!xb_machine_check_one_arg(stack, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* TEXT */ if (xb_opcode_cmp_str(&op)) return xb_stack_push_bool(stack, _xb_opcode_get_str(&op) == NULL, error); /* INTE */ if (_xb_opcode_cmp_int(&op)) return xb_stack_push_bool(stack, _xb_opcode_get_val(&op) == 0, error); /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot invert %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op))); } return FALSE; } static gboolean xb_machine_func_ge_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_val_or_str, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* TEXT:TEXT */ if (xb_opcode_cmp_str(&op1) && xb_opcode_cmp_str(&op2)) { return xb_stack_push_bool( stack, g_strcmp0(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)) >= 0, error); } /* INTE:INTE */ if (_xb_opcode_cmp_int(&op1) && _xb_opcode_cmp_int(&op2)) { return xb_stack_push_bool(stack, _xb_opcode_get_val(&op2) >= _xb_opcode_get_val(&op1), error); } /* INTE:TEXT */ if (_xb_opcode_cmp_int(&op1) && xb_opcode_cmp_str(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val >= _xb_opcode_get_val(&op1), error); } /* TEXT:INTE */ if (xb_opcode_cmp_str(&op1) && _xb_opcode_cmp_int(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op1) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val >= _xb_opcode_get_val(&op2), error); } /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot compare %s and %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op1)), xb_opcode_kind_to_string(_xb_opcode_get_kind(&op2))); } return FALSE; } static gboolean xb_machine_func_contains_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, xb_opcode_cmp_str, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* TEXT:TEXT */ return xb_stack_push_bool( stack, xb_string_contains(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)), error); } static gboolean xb_machine_func_starts_with_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, xb_opcode_cmp_str, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* TEXT:TEXT */ return xb_stack_push_bool( stack, g_str_has_prefix(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)), error); } static gboolean xb_machine_func_ends_with_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, xb_opcode_cmp_str, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* TEXT:TEXT */ return xb_stack_push_bool( stack, g_str_has_suffix(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)), error); } static gboolean xb_machine_func_number_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { guint64 val = 0; g_auto(XbOpcode) op = XB_OPCODE_INIT(); if (!xb_machine_check_one_arg(stack, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* TEXT */ if (_xb_opcode_get_str(&op) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_machine_stack_push_integer(self, stack, val, error); } static gboolean xb_machine_func_strlen_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op = XB_OPCODE_INIT(); if (!xb_machine_check_one_arg(stack, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* TEXT */ if (_xb_opcode_get_str(&op) == NULL) return xb_stack_push_bool(stack, FALSE, error); return xb_machine_stack_push_integer(self, stack, strlen(_xb_opcode_get_str(&op)), error); } static gboolean xb_machine_func_string_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { gchar *tmp; g_auto(XbOpcode) op = XB_OPCODE_INIT(); if (!xb_machine_check_one_arg(stack, _xb_opcode_cmp_int, error)) return FALSE; if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* INTE */ tmp = g_strdup_printf("%" G_GUINT32_FORMAT, _xb_opcode_get_val(&op)); return xb_machine_stack_push_text_steal(self, stack, tmp, error); } static gboolean xb_machine_func_in_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { XbOpcode *op_needle; const gchar *haystack[XB_MACHINE_STACK_LEVELS_MAX + 1] = {NULL}; g_auto(XbOpcode) op = XB_OPCODE_INIT(); guint8 level = G_MAXUINT8; guint nr_args = 0; /* get the size of the haystack, ensuring we only have strings */ for (guint i = xb_stack_get_size(stack) - 1; i > 0; i--) { XbOpcode *op_tmp = xb_stack_peek(stack, i); /* this is a hack as we do not get the current @level */ if (level != G_MAXUINT8) { if (_xb_opcode_get_level(op_tmp) != level) break; } else { level = _xb_opcode_get_level(op_tmp); } if (!xb_opcode_cmp_str(op_tmp)) { if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s type not supported", xb_opcode_kind_to_string(_xb_opcode_get_kind(op_tmp))); } return FALSE; } nr_args++; } /* ensure the needle is also a string */ op_needle = xb_stack_peek(stack, xb_stack_get_size(stack) - (nr_args + 1)); if (!xb_opcode_cmp_str(op_needle)) { if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s type not supported", xb_opcode_kind_to_string(_xb_opcode_get_kind(op_needle))); } return FALSE; } /* build the haystack */ for (guint i = 0; i < nr_args; i++) { g_auto(XbOpcode) op_tmp = XB_OPCODE_INIT(); if (!xb_machine_stack_pop(self, stack, &op_tmp, error)) return FALSE; haystack[i] = _xb_opcode_get_str(&op_tmp); } /* get the needle */ if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* found */ return xb_stack_push_bool(stack, g_strv_contains(haystack, _xb_opcode_get_str(&op)), error); } static void xb_machine_opcode_fixup_free(XbMachineOpcodeFixupItem *item) { if (item->user_data_free != NULL) item->user_data_free(item->user_data); g_slice_free(XbMachineOpcodeFixupItem, item); } static void xb_machine_func_free(XbMachineMethodItem *item) { if (item->user_data_free != NULL) item->user_data_free(item->user_data); g_free(item->name); g_slice_free(XbMachineMethodItem, item); } static void xb_machine_text_handler_free(XbMachineTextHandlerItem *item) { if (item->user_data_free != NULL) item->user_data_free(item->user_data); g_slice_free(XbMachineTextHandlerItem, item); } static void xb_machine_operator_free(XbMachineOperator *op) { g_free(op->str); g_free(op->name); g_slice_free(XbMachineOperator, op); } static void xb_machine_init(XbMachine *self) { XbMachinePrivate *priv = GET_PRIVATE(self); priv->stack_size = 10; priv->methods = g_ptr_array_new_with_free_func((GDestroyNotify)xb_machine_func_free); priv->operators = g_ptr_array_new_with_free_func((GDestroyNotify)xb_machine_operator_free); priv->text_handlers = g_ptr_array_new_with_free_func((GDestroyNotify)xb_machine_text_handler_free); priv->opcode_fixup = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)xb_machine_opcode_fixup_free); priv->opcode_tokens = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); /* built-in functions */ xb_machine_add_method(self, "and", 2, xb_machine_func_and_cb, NULL, NULL); xb_machine_add_method(self, "or", 2, xb_machine_func_or_cb, NULL, NULL); xb_machine_add_method(self, "eq", 2, xb_machine_func_eq_cb, NULL, NULL); xb_machine_add_method(self, "ne", 2, xb_machine_func_ne_cb, NULL, NULL); xb_machine_add_method(self, "lt", 2, xb_machine_func_lt_cb, NULL, NULL); xb_machine_add_method(self, "gt", 2, xb_machine_func_gt_cb, NULL, NULL); xb_machine_add_method(self, "le", 2, xb_machine_func_le_cb, NULL, NULL); xb_machine_add_method(self, "ge", 2, xb_machine_func_ge_cb, NULL, NULL); xb_machine_add_method(self, "not", 1, xb_machine_func_not_cb, NULL, NULL); xb_machine_add_method(self, "lower-case", 1, xb_machine_func_lower_cb, NULL, NULL); xb_machine_add_method(self, "upper-case", 1, xb_machine_func_upper_cb, NULL, NULL); xb_machine_add_method(self, "contains", 2, xb_machine_func_contains_cb, NULL, NULL); xb_machine_add_method(self, "starts-with", 2, xb_machine_func_starts_with_cb, NULL, NULL); xb_machine_add_method(self, "ends-with", 2, xb_machine_func_ends_with_cb, NULL, NULL); xb_machine_add_method(self, "string", 1, xb_machine_func_string_cb, NULL, NULL); xb_machine_add_method(self, "number", 1, xb_machine_func_number_cb, NULL, NULL); xb_machine_add_method(self, "string-length", 1, xb_machine_func_strlen_cb, NULL, NULL); xb_machine_add_method(self, "in", 0, xb_machine_func_in_cb, NULL, NULL); /* built-in operators */ xb_machine_add_operator(self, " and ", "and"); xb_machine_add_operator(self, " or ", "or"); xb_machine_add_operator(self, "&&", "and"); xb_machine_add_operator(self, "||", "or"); xb_machine_add_operator(self, "!=", "ne"); xb_machine_add_operator(self, "<=", "le"); xb_machine_add_operator(self, ">=", "ge"); xb_machine_add_operator(self, "==", "eq"); xb_machine_add_operator(self, "=", "eq"); xb_machine_add_operator(self, ">", "gt"); xb_machine_add_operator(self, "<", "lt"); } static void xb_machine_finalize(GObject *obj) { XbMachine *self = XB_MACHINE(obj); XbMachinePrivate *priv = GET_PRIVATE(self); g_ptr_array_unref(priv->methods); g_ptr_array_unref(priv->operators); g_ptr_array_unref(priv->text_handlers); g_hash_table_unref(priv->opcode_fixup); g_hash_table_unref(priv->opcode_tokens); G_OBJECT_CLASS(xb_machine_parent_class)->finalize(obj); } static void xb_machine_class_init(XbMachineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = xb_machine_finalize; } /** * xb_machine_new: * * Creates a new virtual machine. * * Returns: a new #XbMachine * * Since: 0.1.1 **/ XbMachine * xb_machine_new(void) { return g_object_new(XB_TYPE_MACHINE, NULL); } libxmlb-0.3.22/src/xb-machine.h000066400000000000000000000117511476425255200162230ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-opcode.h" #include "xb-stack.h" #include "xb-value-bindings.h" G_BEGIN_DECLS #define XB_TYPE_MACHINE (xb_machine_get_type()) G_DECLARE_DERIVABLE_TYPE(XbMachine, xb_machine, XB, MACHINE, GObject) struct _XbMachineClass { GObjectClass parent_class; /*< private >*/ void (*_xb_reserved1)(void); void (*_xb_reserved2)(void); void (*_xb_reserved3)(void); void (*_xb_reserved4)(void); void (*_xb_reserved5)(void); void (*_xb_reserved6)(void); void (*_xb_reserved7)(void); }; /** * XbMachineDebugFlags: * @XB_MACHINE_DEBUG_FLAG_NONE: No debug flags to use * @XB_MACHINE_DEBUG_FLAG_SHOW_STACK: Show the stack addition and removal * @XB_MACHINE_DEBUG_FLAG_SHOW_PARSING: Show the XPath predicate parsing * @XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER: Show the optimizer operation * @XB_MACHINE_DEBUG_FLAG_SHOW_SLOW_PATH: Show the query slow paths * * The flags to control the amount of debugging is generated. **/ typedef enum { XB_MACHINE_DEBUG_FLAG_NONE = 0, XB_MACHINE_DEBUG_FLAG_SHOW_STACK = 1 << 0, XB_MACHINE_DEBUG_FLAG_SHOW_PARSING = 1 << 1, XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER = 1 << 2, XB_MACHINE_DEBUG_FLAG_SHOW_SLOW_PATH = 1 << 3, /*< private >*/ XB_MACHINE_DEBUG_FLAG_LAST } XbMachineDebugFlags; /** * XbMachineParseFlags: * @XB_MACHINE_PARSE_FLAG_NONE: No flags set * @XB_MACHINE_PARSE_FLAG_OPTIMIZE: Run an optimization pass on the predicate * * The flags to control the parsing behaviour. **/ typedef enum { XB_MACHINE_PARSE_FLAG_NONE = 0, XB_MACHINE_PARSE_FLAG_OPTIMIZE = 1 << 0, /*< private >*/ XB_MACHINE_PARSE_FLAG_LAST } XbMachineParseFlags; typedef gboolean (*XbMachineOpcodeFixupFunc)(XbMachine *self, XbStack *opcodes, gpointer user_data, GError **error); /* when next breaking API add @level here */ typedef gboolean (*XbMachineTextHandlerFunc)(XbMachine *self, XbStack *opcodes, const gchar *text, gboolean *handled, gpointer user_data, GError **error); /* when next breaking API add @level here */ typedef gboolean (*XbMachineMethodFunc)(XbMachine *self, XbStack *stack, gboolean *result_unused, gpointer exec_data, gpointer user_data, GError **error); XbMachine * xb_machine_new(void); void xb_machine_set_debug_flags(XbMachine *self, XbMachineDebugFlags flags) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(xb_machine_parse_full) XbStack * xb_machine_parse(XbMachine *self, const gchar *text, gssize text_len, GError **error) G_GNUC_NON_NULL(1, 2); XbStack * xb_machine_parse_full(XbMachine *self, const gchar *text, gssize text_len, XbMachineParseFlags flags, GError **error) G_GNUC_NON_NULL(1, 2); G_DEPRECATED_FOR(xb_machine_run_with_bindings) gboolean xb_machine_run(XbMachine *self, XbStack *opcodes, gboolean *result, gpointer exec_data, GError **error) G_GNUC_NON_NULL(1, 2, 3); gboolean xb_machine_run_with_bindings(XbMachine *self, XbStack *opcodes, XbValueBindings *bindings, gboolean *result, gpointer exec_data, GError **error) G_GNUC_NON_NULL(1, 2); void xb_machine_add_opcode_fixup(XbMachine *self, const gchar *opcodes_sig, XbMachineOpcodeFixupFunc fixup_cb, gpointer user_data, GDestroyNotify user_data_free) G_GNUC_NON_NULL(1, 2, 3); void xb_machine_add_text_handler(XbMachine *self, XbMachineTextHandlerFunc handler_cb, gpointer user_data, GDestroyNotify user_data_free) G_GNUC_NON_NULL(1, 2); void xb_machine_add_method(XbMachine *self, const gchar *name, guint n_opcodes, XbMachineMethodFunc method_cb, gpointer user_data, GDestroyNotify user_data_free) G_GNUC_NON_NULL(1, 2, 4); void xb_machine_add_operator(XbMachine *self, const gchar *str, const gchar *name) G_GNUC_NON_NULL(1, 2, 3); gboolean xb_machine_opcode_func_init(XbMachine *self, XbOpcode *opcode, const gchar *func_name) G_GNUC_NON_NULL(1, 2, 3); gboolean xb_machine_stack_pop(XbMachine *self, XbStack *stack, XbOpcode *opcode_out, GError **error) G_GNUC_NON_NULL(1, 2, 3); gboolean xb_machine_stack_push(XbMachine *self, XbStack *stack, XbOpcode **opcode_out, GError **error) G_GNUC_NON_NULL(1, 2, 3); gboolean xb_machine_stack_push_text(XbMachine *self, XbStack *stack, const gchar *str, GError **error) G_GNUC_NON_NULL(1, 2); gboolean xb_machine_stack_push_text_static(XbMachine *self, XbStack *stack, const gchar *str, GError **error) G_GNUC_NON_NULL(1, 2); gboolean xb_machine_stack_push_text_steal(XbMachine *self, XbStack *stack, gchar *str, GError **error) G_GNUC_NON_NULL(1, 2, 3); gboolean xb_machine_stack_push_integer(XbMachine *self, XbStack *stack, guint32 val, GError **error) G_GNUC_NON_NULL(1, 2); void xb_machine_set_stack_size(XbMachine *self, guint stack_size) G_GNUC_NON_NULL(1); guint xb_machine_get_stack_size(XbMachine *self) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-node-private.h000066400000000000000000000005221476425255200172060ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-node.h" #include "xb-silo-private.h" G_BEGIN_DECLS XbNode * xb_node_new(XbSilo *silo, XbSiloNode *sn) G_GNUC_NON_NULL(1); XbSiloNode * xb_node_get_sn(XbNode *self) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-node-query.c000066400000000000000000000266351476425255200167110ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbNode" #include "config.h" #include #include #include "xb-node-query.h" #include "xb-node-silo.h" #include "xb-silo-export-private.h" #include "xb-silo-query-private.h" /** * xb_node_query: * @self: a #XbNode * @xpath: an XPath, e.g. `id[abe.desktop]` * @limit: maximum number of results to return, or 0 for "all" * @error: the #GError, or %NULL * * Searches the silo using an XPath query, returning up to @limit results. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound * * Since: 0.1.0 **/ GPtrArray * xb_node_query(XbNode *self, const gchar *xpath, guint limit, GError **error) { g_return_val_if_fail(XB_IS_NODE(self), NULL); g_return_val_if_fail(xpath != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return xb_silo_query_with_root(xb_node_get_silo(self), self, xpath, limit, error); } /** * xb_node_query_full: * @self: a #XbNode * @query: an #XbQuery * @error: the #GError, or %NULL * * Searches the silo using a prepared query. To search using a query with * bound values, use xb_node_query_with_context(). * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound * * Since: 0.1.4 **/ GPtrArray * xb_node_query_full(XbNode *self, XbQuery *query, GError **error) { g_return_val_if_fail(XB_IS_NODE(self), NULL); g_return_val_if_fail(XB_IS_QUERY(query), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return xb_silo_query_with_root_full(xb_node_get_silo(self), self, query, NULL, FALSE, error); } /** * xb_node_query_with_context: * @self: a #XbNode * @query: an #XbQuery * @context: (nullable) (transfer none): context including values bound to opcodes of type * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if * the query doesn’t need any context * @error: the #GError, or %NULL * * Searches the silo using a prepared query, substituting values from the * bindings in @context for bound opcodes as needed. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound * * Since: 0.3.0 **/ GPtrArray * xb_node_query_with_context(XbNode *self, XbQuery *query, XbQueryContext *context, GError **error) { g_return_val_if_fail(XB_IS_NODE(self), NULL); g_return_val_if_fail(XB_IS_QUERY(query), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return xb_silo_query_with_root_full(xb_node_get_silo(self), self, query, context, FALSE, error); } /** * xb_node_query_first_full: * @self: a #XbNode * @query: an #XbQuery * @error: the #GError, or %NULL * * Searches the silo using a prepared query, returning up to one result. To * search using a query with bound values, use * xb_node_query_first_with_context(). * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer full): a #XbNode, or %NULL if unfound * * Since: 0.1.11 **/ XbNode * xb_node_query_first_full(XbNode *self, XbQuery *query, GError **error) { return xb_node_query_first_with_context(self, query, NULL, error); } /** * xb_node_query_first_with_context: * @self: a #XbNode * @query: an #XbQuery * @context: (nullable) (transfer none): context including values bound to opcodes of type * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if * the query doesn’t need any context * @error: the #GError, or %NULL * * Searches the silo using a prepared query, returning up to one result. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer full): a #XbNode, or %NULL if unfound * * Since: 0.3.0 **/ XbNode * xb_node_query_first_with_context(XbNode *self, XbQuery *query, XbQueryContext *context, GError **error) { g_autoptr(GPtrArray) results = NULL; g_return_val_if_fail(XB_IS_NODE(self), NULL); g_return_val_if_fail(XB_IS_QUERY(query), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* nodes don't have to include themselves as part of the query */ results = xb_silo_query_with_root_full(xb_node_get_silo(self), self, query, context, TRUE, error); if (results == NULL) return NULL; return g_object_ref(g_ptr_array_index(results, 0)); } /** * xb_node_query_first: * @self: a #XbNode * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` * @error: the #GError, or %NULL * * Searches the node using an XPath query, returning up to one result. * * Please note: Only a tiny subset of XPath 1.0 is supported. * * Returns: (transfer full): a #XbNode, or %NULL if unfound * * Since: 0.1.0 **/ XbNode * xb_node_query_first(XbNode *self, const gchar *xpath, GError **error) { g_autoptr(GPtrArray) results = NULL; g_return_val_if_fail(XB_IS_NODE(self), NULL); g_return_val_if_fail(xpath != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* nodes don't have to include themselves as part of the query */ results = xb_silo_query_with_root(xb_node_get_silo(self), self, xpath, 1, error); if (results == NULL) return NULL; return g_object_ref(g_ptr_array_index(results, 0)); } /** * xb_node_query_text: * @self: a #XbNode * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` * @error: the #GError, or %NULL * * Searches the node using an XPath query, returning up to one result. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer none): a string, or %NULL if unfound * * Since: 0.1.0 **/ const gchar * xb_node_query_text(XbNode *self, const gchar *xpath, GError **error) { XbSilo *silo; g_autoptr(GPtrArray) results = NULL; XbSiloNode *sn; g_return_val_if_fail(XB_IS_NODE(self), NULL); g_return_val_if_fail(xpath != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); silo = xb_node_get_silo(self); results = xb_silo_query_sn_with_root(silo, self, xpath, 1, error); if (results == NULL) return NULL; sn = g_ptr_array_index(results, 0); if (xb_silo_node_get_text_idx(sn) == XB_SILO_UNSET) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no text data"); return NULL; } return xb_silo_from_strtab(silo, xb_silo_node_get_text_idx(sn), error); } /** * xb_node_query_attr: * @self: a #XbNode * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` * @name: an attribute name, e.g. `type` * @error: the #GError, or %NULL * * Searches the node using an XPath query, returning up to one result. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer none): a string, or %NULL if unfound * * Since: 0.1.0 **/ const gchar * xb_node_query_attr(XbNode *self, const gchar *xpath, const gchar *name, GError **error) { XbSiloNodeAttr *a; XbSilo *silo; g_autoptr(GPtrArray) results = NULL; XbSiloNode *sn; g_return_val_if_fail(XB_IS_NODE(self), NULL); g_return_val_if_fail(xpath != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); silo = xb_node_get_silo(self); results = xb_silo_query_sn_with_root(silo, self, xpath, 1, error); if (results == NULL) return NULL; sn = g_ptr_array_index(results, 0); a = xb_silo_get_node_attr_by_str(silo, sn, name); if (a == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no text data"); return NULL; } return xb_silo_from_strtab(silo, a->attr_value, error); } /** * xb_node_query_export: * @self: a #XbNode * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` * @error: the #GError, or %NULL * * Searches the node using an XPath query, returning an XML string of the * result and any children. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer none): a string, or %NULL if unfound * * Since: 0.1.0 **/ gchar * xb_node_query_export(XbNode *self, const gchar *xpath, GError **error) { GString *xml; XbSilo *silo; g_autoptr(GPtrArray) results = NULL; XbSiloNode *sn; g_return_val_if_fail(XB_IS_NODE(self), NULL); g_return_val_if_fail(xpath != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); silo = xb_node_get_silo(self); results = xb_silo_query_sn_with_root(silo, self, xpath, 1, error); if (results == NULL) return NULL; sn = g_ptr_array_index(results, 0); xml = xb_silo_export_with_root(silo, sn, XB_NODE_EXPORT_FLAG_NONE, error); if (xml == NULL) return NULL; return g_string_free(xml, FALSE); } /** * xb_node_query_text_as_uint: * @self: a #XbNode * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` * @error: the #GError, or %NULL * * Searches the node using an XPath query, returning up to one result. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: a #guint64, or %G_MAXUINT64 if unfound * * Since: 0.1.0 **/ guint64 xb_node_query_text_as_uint(XbNode *self, const gchar *xpath, GError **error) { const gchar *tmp; g_return_val_if_fail(XB_IS_NODE(self), G_MAXUINT64); g_return_val_if_fail(xpath != NULL, G_MAXUINT64); g_return_val_if_fail(error == NULL || *error == NULL, G_MAXUINT64); tmp = xb_node_query_text(self, xpath, error); if (tmp == NULL) return G_MAXUINT64; if (g_str_has_prefix(tmp, "0x")) return g_ascii_strtoull(tmp + 2, NULL, 16); return g_ascii_strtoull(tmp, NULL, 10); } /** * xb_node_query_attr_as_uint: * @self: a #XbNode * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` * @name: an attribute name, e.g. `type` * @error: the #GError, or %NULL * * Searches the node using an XPath query, returning up to one result. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: a #guint64, or %G_MAXUINT64 if unfound * * Since: 0.1.0 **/ guint64 xb_node_query_attr_as_uint(XbNode *self, const gchar *xpath, const gchar *name, GError **error) { const gchar *tmp; g_return_val_if_fail(XB_IS_NODE(self), G_MAXUINT64); g_return_val_if_fail(xpath != NULL, G_MAXUINT64); g_return_val_if_fail(error == NULL || *error == NULL, G_MAXUINT64); tmp = xb_node_query_attr(self, xpath, name, error); if (tmp == NULL) return G_MAXUINT64; if (g_str_has_prefix(tmp, "0x")) return g_ascii_strtoull(tmp + 2, NULL, 16); return g_ascii_strtoull(tmp, NULL, 10); } libxmlb-0.3.22/src/xb-node-query.h000066400000000000000000000030111476425255200166750ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "xb-node.h" #include "xb-query-context.h" #include "xb-query.h" G_BEGIN_DECLS GPtrArray * xb_node_query(XbNode *self, const gchar *xpath, guint limit, GError **error) G_GNUC_NON_NULL(1, 2); GPtrArray * xb_node_query_full(XbNode *self, XbQuery *query, GError **error) G_GNUC_NON_NULL(1, 2); GPtrArray * xb_node_query_with_context(XbNode *self, XbQuery *query, XbQueryContext *context, GError **error) G_GNUC_NON_NULL(1, 2, 3); XbNode * xb_node_query_first(XbNode *self, const gchar *xpath, GError **error) G_GNUC_NON_NULL(1, 2); XbNode * xb_node_query_first_full(XbNode *self, XbQuery *query, GError **error) G_GNUC_NON_NULL(1, 2); XbNode * xb_node_query_first_with_context(XbNode *self, XbQuery *query, XbQueryContext *context, GError **error) G_GNUC_NON_NULL(1, 2); const gchar * xb_node_query_text(XbNode *self, const gchar *xpath, GError **error) G_GNUC_NON_NULL(1, 2); guint64 xb_node_query_text_as_uint(XbNode *self, const gchar *xpath, GError **error) G_GNUC_NON_NULL(1, 2); const gchar * xb_node_query_attr(XbNode *self, const gchar *xpath, const gchar *name, GError **error) G_GNUC_NON_NULL(1, 2, 3); guint64 xb_node_query_attr_as_uint(XbNode *self, const gchar *xpath, const gchar *name, GError **error) G_GNUC_NON_NULL(1, 2, 3); gchar * xb_node_query_export(XbNode *self, const gchar *xpath, GError **error) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-node-silo.h000066400000000000000000000004011476425255200164760ustar00rootroot00000000000000/* * Copyright 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-node.h" #include "xb-silo.h" G_BEGIN_DECLS XbSilo * xb_node_get_silo(XbNode *self) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-node.c000066400000000000000000000430411476425255200155340ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbNode" #include "config.h" #include #include #include "xb-node-private.h" #include "xb-node-silo.h" #include "xb-silo-export-private.h" typedef struct { XbSilo *silo; XbSiloNode *sn; } XbNodePrivate; G_DEFINE_TYPE_WITH_PRIVATE(XbNode, xb_node, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_node_get_instance_private(o)) /** * XbNodeAttrIter: * * A #XbNodeAttrIter structure represents an iterator that can be used * to iterate over the attributes of a #XbNode. #XbNodeAttrIter * structures are typically allocated on the stack and then initialized * with xb_node_attr_iter_init(). * * The iteration order of a #XbNodeAttrIter is not defined. * * Since: 0.3.4 */ typedef struct { XbNode *node; guint8 position; gpointer dummy3; gpointer dummy4; gpointer dummy5; gpointer dummy6; } RealAttrIter; G_STATIC_ASSERT(sizeof(XbNodeAttrIter) == sizeof(RealAttrIter)); /** * XbNodeChildIter: * * A #XbNodeChildIter structure represents an iterator that can be used * to iterate over the children of a #XbNode. #XbNodeChildIter * structures are typically allocated on the stack and then initialized * with xb_node_child_iter_init(). * * Since: 0.3.4 */ typedef struct { XbNode *node; XbSiloNode *position; gboolean first_iter; gpointer dummy4; gpointer dummy5; gpointer dummy6; } RealChildIter; G_STATIC_ASSERT(sizeof(XbNodeChildIter) == sizeof(RealChildIter)); /** * xb_node_get_data: * @self: a #XbNode * @key: a string key, e.g. `fwupd::RemoteId` * * Gets any data that has been set on the node using xb_node_set_data(). * * This will only work across queries to the associated silo if the silo has * its #XbSilo:enable-node-cache property set to %TRUE. Otherwise a new #XbNode * may be constructed for future queries which return the same element as a * result. * * Returns: (transfer none): a #GBytes, or %NULL if not found * * Since: 0.1.0 **/ GBytes * xb_node_get_data(XbNode *self, const gchar *key) { XbNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_NODE(self), NULL); g_return_val_if_fail(key != NULL, NULL); g_return_val_if_fail(priv->silo, NULL); return g_object_get_data(G_OBJECT(self), key); } /** * xb_node_set_data: * @self: a #XbNode * @key: a string key, e.g. `fwupd::RemoteId` * @data: a #GBytes * * Sets some data on the node which can be retrieved using xb_node_get_data(). * * This will only work across queries to the associated silo if the silo has * its #XbSilo:enable-node-cache property set to %TRUE. Otherwise a new #XbNode * may be constructed for future queries which return the same element as a * result. * * Since: 0.1.0 **/ void xb_node_set_data(XbNode *self, const gchar *key, GBytes *data) { XbNodePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_NODE(self)); g_return_if_fail(key != NULL); g_return_if_fail(data != NULL); g_return_if_fail(priv->silo); g_object_set_data_full(G_OBJECT(self), key, g_bytes_ref(data), (GDestroyNotify)g_bytes_unref); } /** * xb_node_get_sn: (skip) * @self: a #XbNode * * Gets the #XbSiloNode for the node. * * Returns: (transfer none): a #XbSiloNode * * Since: 0.1.0 **/ XbSiloNode * xb_node_get_sn(XbNode *self) { XbNodePrivate *priv = GET_PRIVATE(self); return priv->sn; } /** * xb_node_get_silo: * @self: a #XbNode * * Gets the #XbSilo for the node. * * Returns: (transfer none): a #XbSilo * * Since: 0.2.0 **/ XbSilo * xb_node_get_silo(XbNode *self) { XbNodePrivate *priv = GET_PRIVATE(self); return priv->silo; } /** * xb_node_get_root: * @self: a #XbNode * * Gets the root node for the node. * * Returns: (transfer full): a #XbNode, or %NULL * * Since: 0.1.0 **/ XbNode * xb_node_get_root(XbNode *self) { XbNodePrivate *priv = GET_PRIVATE(self); XbSiloNode *sn; g_return_val_if_fail(XB_IS_NODE(self), NULL); sn = xb_silo_get_root_node(priv->silo, NULL); if (sn == NULL) return NULL; return xb_silo_create_node(priv->silo, sn, FALSE); } /** * xb_node_get_parent: * @self: a #XbNode * * Gets the parent node for the current node. * * Returns: (transfer full): a #XbNode, or %NULL * * Since: 0.1.0 **/ XbNode * xb_node_get_parent(XbNode *self) { XbNodePrivate *priv = GET_PRIVATE(self); XbSiloNode *sn; g_return_val_if_fail(XB_IS_NODE(self), NULL); if (priv->sn == NULL) return NULL; sn = xb_silo_get_parent_node(priv->silo, priv->sn, NULL); if (sn == NULL) return NULL; return xb_silo_create_node(priv->silo, sn, FALSE); } /** * xb_node_get_next: * @self: a #XbNode * * Gets the next sibling node for the current node. * * Returns: (transfer full): a #XbNode, or %NULL * * Since: 0.1.0 **/ XbNode * xb_node_get_next(XbNode *self) { XbNodePrivate *priv = GET_PRIVATE(self); XbSiloNode *sn; g_return_val_if_fail(XB_IS_NODE(self), NULL); if (priv->sn == NULL) return NULL; sn = xb_silo_get_next_node(priv->silo, priv->sn, NULL); if (sn == NULL) return NULL; return xb_silo_create_node(priv->silo, sn, FALSE); } /** * xb_node_get_child: * @self: a #XbNode * * Gets the first child node for the current node. * * Returns: (transfer full): a #XbNode, or %NULL * * Since: 0.1.0 **/ XbNode * xb_node_get_child(XbNode *self) { XbNodePrivate *priv = GET_PRIVATE(self); XbSiloNode *sn; g_return_val_if_fail(XB_IS_NODE(self), NULL); if (priv->sn == NULL) return NULL; sn = xb_silo_get_child_node(priv->silo, priv->sn, NULL); if (sn == NULL) return NULL; return xb_silo_create_node(priv->silo, sn, FALSE); } /** * xb_node_get_children: * @self: a #XbNode * * Gets all the children for the current node. * * Returns: (transfer container) (element-type XbNode): an array of children * * Since: 0.1.0 **/ GPtrArray * xb_node_get_children(XbNode *self) { XbNode *n; GPtrArray *array = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); /* add all children */ n = xb_node_get_child(self); while (n != NULL) { g_ptr_array_add(array, n); n = xb_node_get_next(n); } return array; } /** * xb_node_child_iter_init: * @iter: an uninitialized #XbNodeChildIter * @self: a #XbNode * * Initializes a child iterator for the node's children and associates * it with @self. * The #XbNodeChildIter structure is typically allocated on the stack * and does not need to be freed explicitly. * * Since: 0.3.4 */ void xb_node_child_iter_init(XbNodeChildIter *iter, XbNode *self) { XbNodePrivate *priv = GET_PRIVATE(self); RealChildIter *ri = (RealChildIter *)iter; g_return_if_fail(iter != NULL); g_return_if_fail(XB_IS_NODE(self)); ri->node = self; ri->position = priv->sn != NULL ? xb_silo_get_child_node(priv->silo, priv->sn, NULL) : NULL; ri->first_iter = TRUE; } /** * xb_node_child_iter_next: * @iter: an initialized #XbNodeAttrIter * @child: (out) (optional) (not nullable): Destination of the returned child * * Returns the current child and advances the iterator. * The retrieved #XbNode child needs to be dereferenced with g_object_unref(). * Example: * |[ * XbNodeChildIter iter; * g_autoptr(XbNode) child = NULL; * * xb_node_child_iter_init (&iter, node); * while (xb_node_child_iter_next (&iter, &child)) { * // do something with the node child * g_clear_pointer (&child, g_object_unref); * } * ]| * * Returns: %FALSE if the last child has been reached. * * Since: 0.3.4 */ gboolean xb_node_child_iter_next(XbNodeChildIter *iter, XbNode **child) { XbNodePrivate *priv; RealChildIter *ri = (RealChildIter *)iter; g_return_val_if_fail(iter != NULL, FALSE); g_return_val_if_fail(child != NULL, FALSE); priv = GET_PRIVATE(ri->node); /* check if the iteration was finished */ if (ri->position == NULL) { *child = NULL; return FALSE; } *child = xb_silo_create_node(priv->silo, ri->position, FALSE); ri->position = xb_silo_get_next_node(priv->silo, ri->position, NULL); return TRUE; } /** * xb_node_child_iter_loop: (skip) * @iter: an initialized #XbNodeAttrIter * @child: (out) (optional) (nullable): Destination of the returned child * * Returns the current child and advances the iterator. * On the first call to this function, the @child pointer is assumed to point * at uninitialised memory. * On any later calls, it is assumed that the same pointers * will be given and that they will point to the memory as set by the * previous call to this function. This allows the previous values to * be freed, as appropriate. * * Example: * |[ * XbNodeChildIter iter; * XbNode *child; * * xb_node_child_iter_init (&iter, node); * while (xb_node_child_iter_loop (&iter, &child)) { * // do something with the node child * // no need to free 'child' unless breaking out of this loop * } * ]| * * Returns: %FALSE if the last child has been reached. * * Since: 0.3.4 */ gboolean xb_node_child_iter_loop(XbNodeChildIter *iter, XbNode **child) { XbNodePrivate *priv; RealChildIter *ri = (RealChildIter *)iter; g_return_val_if_fail(iter != NULL, FALSE); g_return_val_if_fail(child != NULL, FALSE); priv = GET_PRIVATE(ri->node); /* unref child from previous iterations, if there were any */ if (ri->first_iter) ri->first_iter = FALSE; else g_object_unref(*child); /* check if the iteration was finished */ if (ri->position == NULL) { *child = NULL; return FALSE; } *child = xb_silo_create_node(priv->silo, ri->position, FALSE); ri->position = xb_silo_get_next_node(priv->silo, ri->position, NULL); return TRUE; } /** * xb_node_get_text: * @self: a #XbNode * * Gets the text data for a specific node. * * Returns: a string, or %NULL for unset * * Since: 0.1.0 **/ const gchar * xb_node_get_text(XbNode *self) { XbNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_NODE(self), NULL); if (priv->sn == NULL) return NULL; if (xb_silo_node_get_text_idx(priv->sn) == XB_SILO_UNSET) return NULL; return xb_silo_from_strtab(priv->silo, xb_silo_node_get_text_idx(priv->sn), NULL); } /** * xb_node_get_text_as_uint: * @self: a #XbNode * * Gets some attribute text data for a specific node. * * Returns: a guint64, or %G_MAXUINT64 if unfound * * Since: 0.1.0 **/ guint64 xb_node_get_text_as_uint(XbNode *self) { const gchar *tmp; g_return_val_if_fail(XB_IS_NODE(self), G_MAXUINT64); tmp = xb_node_get_text(self); if (tmp == NULL) return G_MAXUINT64; if (g_str_has_prefix(tmp, "0x")) return g_ascii_strtoull(tmp + 2, NULL, 16); return g_ascii_strtoull(tmp, NULL, 10); } /** * xb_node_get_tail: * @self: a #XbNode * * Gets the tail data for a specific node. * * Returns: a string, or %NULL for unset * * Since: 0.1.12 **/ const gchar * xb_node_get_tail(XbNode *self) { XbNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_NODE(self), NULL); if (priv->sn == NULL) return NULL; if (xb_silo_node_get_tail_idx(priv->sn) == XB_SILO_UNSET) return NULL; return xb_silo_from_strtab(priv->silo, xb_silo_node_get_tail_idx(priv->sn), NULL); } /** * xb_node_get_element: * @self: a #XbNode * * Gets the element name for a specific node. * * Returns: a string, or %NULL for the root node * * Since: 0.1.0 **/ const gchar * xb_node_get_element(XbNode *self) { XbNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_NODE(self), NULL); if (priv->sn == NULL) return NULL; return xb_silo_get_node_element(priv->silo, priv->sn, NULL); } /** * xb_node_get_attr: * @self: a #XbNode * @name: an attribute name, e.g. "type" * * Gets some attribute text data for a specific node. * * Returns: a string, or %NULL for unset * * Since: 0.1.0 **/ const gchar * xb_node_get_attr(XbNode *self, const gchar *name) { XbNodePrivate *priv = GET_PRIVATE(self); XbSiloNodeAttr *a; g_return_val_if_fail(XB_IS_NODE(self), NULL); g_return_val_if_fail(name != NULL, NULL); if (priv->sn == NULL) return NULL; a = xb_silo_get_node_attr_by_str(priv->silo, priv->sn, name); if (a == NULL) return NULL; return xb_silo_from_strtab(priv->silo, a->attr_value, NULL); } /** * xb_node_get_attr_as_uint: * @self: a #XbNode * @name: an attribute name, e.g. `type` * * Gets some attribute text data for a specific node. * * Returns: a guint64, or %G_MAXUINT64 if unfound * * Since: 0.1.0 **/ guint64 xb_node_get_attr_as_uint(XbNode *self, const gchar *name) { const gchar *tmp; g_return_val_if_fail(XB_IS_NODE(self), G_MAXUINT64); g_return_val_if_fail(name != NULL, G_MAXUINT64); tmp = xb_node_get_attr(self, name); if (tmp == NULL) return G_MAXUINT64; if (g_str_has_prefix(tmp, "0x")) return g_ascii_strtoull(tmp + 2, NULL, 16); return g_ascii_strtoull(tmp, NULL, 10); } /** * xb_node_attr_iter_init: * @iter: an uninitialized #XbNodeAttrIter * @self: a #XbNode * * Initializes a name/value pair iterator for the node attributes * and associates it with @self. * The #XbNodeAttrIter structure is typically allocated on the stack * and does not need to be freed explicitly. * * Since: 0.3.4 */ void xb_node_attr_iter_init(XbNodeAttrIter *iter, XbNode *self) { XbNodePrivate *priv = GET_PRIVATE(self); RealAttrIter *ri = (RealAttrIter *)iter; g_return_if_fail(iter != NULL); g_return_if_fail(XB_IS_NODE(self)); ri->node = self; ri->position = priv->sn != NULL ? xb_silo_node_get_attr_count(priv->sn) : 0; } /** * xb_node_attr_iter_next: * @iter: an initialized #XbNodeAttrIter * @name: (out) (optional) (not nullable): Destination of the returned attribute name * @value: (out) (optional) (not nullable): Destination of the returned attribute value * * Returns the current attribute name and value and advances the iterator. * Example: * |[ * XbNodeAttrIter iter; * const gchar *attr_name, *attr_value; * * xb_node_attr_iter_init (&iter, node); * while (xb_node_attr_iter_next (&iter, &attr_name, &attr_value)) { * // use attr_name and attr_value; no need to free them * } * ]| * * Returns: %TRUE if there are more attributes. * * Since: 0.3.4 */ gboolean xb_node_attr_iter_next(XbNodeAttrIter *iter, const gchar **name, const gchar **value) { XbSiloNodeAttr *a; XbNodePrivate *priv; RealAttrIter *ri = (RealAttrIter *)iter; g_return_val_if_fail(iter != NULL, FALSE); priv = GET_PRIVATE(ri->node); /* check if the iteration was finished */ if (ri->position == 0) { if (name != NULL) *name = NULL; if (value != NULL) *value = NULL; return FALSE; } ri->position--; a = xb_silo_node_get_attr(priv->sn, ri->position); if (name != NULL) *name = xb_silo_from_strtab(priv->silo, a->attr_name, NULL); if (value != NULL) *value = xb_silo_from_strtab(priv->silo, a->attr_value, NULL); return TRUE; } /** * xb_node_get_depth: * @self: a #XbNode * * Gets the depth of the node to a root. * * Returns: a integer, where 0 is the root node itself. * * Since: 0.1.0 **/ guint xb_node_get_depth(XbNode *self) { XbNodePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_NODE(self), 0); if (priv->sn == NULL) return 0; return xb_silo_get_node_depth(priv->silo, priv->sn); } /** * xb_node_export: * @self: a #XbNode * @flags: some #XbNodeExportFlags, e.g. #XB_NODE_EXPORT_FLAG_NONE * @error: the #GError, or %NULL * * Exports the node back to XML. * * Returns: XML data, or %NULL for an error * * Since: 0.1.0 **/ gchar * xb_node_export(XbNode *self, XbNodeExportFlags flags, GError **error) { GString *xml; g_return_val_if_fail(XB_IS_NODE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); xml = xb_silo_export_with_root(xb_node_get_silo(self), xb_node_get_sn(self), flags, error); if (xml == NULL) return NULL; return g_string_free(xml, FALSE); } /** * xb_node_transmogrify: * @self: a #XbNode * @func_text: (scope call): (allow-none): a #XbBuilderNodeTraverseFunc * @func_tail: (scope call): (allow-none): a #XbBuilderNodeTraverseFunc * @user_data: user pointer to pass to @func, or %NULL * * Traverses a tree starting from @self. It calls the given functions for each * node visited. This allows transmogrification of the source, for instance * converting the XML description to PangoMarkup or even something completely * different like markdown. * * The traversal can be halted at any point by returning TRUE from @func. * * Returns: %TRUE if all nodes were visited * * Since: 0.1.12 **/ gboolean xb_node_transmogrify(XbNode *self, XbNodeTransmogrifyFunc func_text, XbNodeTransmogrifyFunc func_tail, gpointer user_data) { g_autoptr(XbNode) n = NULL; g_return_val_if_fail(XB_IS_NODE(self), FALSE); /* all siblings */ n = g_object_ref(self); while (n != NULL) { g_autoptr(XbNode) c = NULL; g_autoptr(XbNode) tmp = NULL; /* head */ if (func_text != NULL) { if (func_text(n, user_data)) return FALSE; } /* all children */ c = xb_node_get_child(n); if (c != NULL) { if (!xb_node_transmogrify(c, func_text, func_tail, user_data)) return FALSE; } /* tail */ if (func_tail != NULL) { if (func_tail(n, user_data)) return FALSE; } /* next sibling */ tmp = xb_node_get_next(n); g_set_object(&n, tmp); } return TRUE; } static void xb_node_init(XbNode *self) { } static void xb_node_finalize(GObject *obj) { G_OBJECT_CLASS(xb_node_parent_class)->finalize(obj); } static void xb_node_class_init(XbNodeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = xb_node_finalize; } /** * xb_node_new: (skip) * @silo: A #XbSilo * @sn: A #XbSiloNode * * Creates a new node. * * Returns: a new #XbNode * * Since: 0.1.0 **/ XbNode * xb_node_new(XbSilo *silo, XbSiloNode *sn) { XbNode *self = g_object_new(XB_TYPE_NODE, NULL); XbNodePrivate *priv = GET_PRIVATE(self); priv->silo = silo; priv->sn = sn; return self; } libxmlb-0.3.22/src/xb-node.h000066400000000000000000000073711476425255200155470ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-compile.h" G_BEGIN_DECLS #define XB_TYPE_NODE (xb_node_get_type()) G_DECLARE_DERIVABLE_TYPE(XbNode, xb_node, XB, NODE, GObject) struct _XbNodeClass { GObjectClass parent_class; /*< private >*/ void (*_xb_reserved1)(void); void (*_xb_reserved2)(void); void (*_xb_reserved3)(void); void (*_xb_reserved4)(void); void (*_xb_reserved5)(void); void (*_xb_reserved6)(void); void (*_xb_reserved7)(void); }; /** * XbNodeExportFlags: * @XB_NODE_EXPORT_FLAG_NONE: No extra flags to use * @XB_NODE_EXPORT_FLAG_ADD_HEADER: Add an XML header to the data * @XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE: Split up children with a newline * @XB_NODE_EXPORT_FLAG_FORMAT_INDENT: Indent the XML by child depth * @XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS: Include the siblings when converting * @XB_NODE_EXPORT_FLAG_ONLY_CHILDREN: Only export the children of the node * @XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY: If node has no children, collapse open and close *tags * * The flags for converting to XML. **/ typedef enum { XB_NODE_EXPORT_FLAG_NONE = 0, /* Since: 0.1.0 */ XB_NODE_EXPORT_FLAG_ADD_HEADER = 1 << 0, /* Since: 0.1.0 */ XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE = 1 << 1, /* Since: 0.1.0 */ XB_NODE_EXPORT_FLAG_FORMAT_INDENT = 1 << 2, /* Since: 0.1.0 */ XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS = 1 << 3, /* Since: 0.1.0 */ XB_NODE_EXPORT_FLAG_ONLY_CHILDREN = 1 << 4, /* Since: 0.1.0 */ XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY = 1 << 5, /* Since: 0.2.2 */ /*< private >*/ XB_NODE_EXPORT_FLAG_LAST } XbNodeExportFlags; typedef struct { /*< private >*/ gpointer dummy1; guint8 dummy2; gpointer dummy3; gpointer dummy4; gpointer dummy5; gpointer dummy6; } XbNodeAttrIter; typedef struct { /*< private >*/ gpointer dummy1; gpointer dummy2; gboolean dummy3; gpointer dummy4; gpointer dummy5; gpointer dummy6; } XbNodeChildIter; typedef gboolean (*XbNodeTransmogrifyFunc)(XbNode *self, gpointer user_data); gboolean xb_node_transmogrify(XbNode *self, XbNodeTransmogrifyFunc func_text, XbNodeTransmogrifyFunc func_tail, gpointer user_data) G_GNUC_NON_NULL(1); gchar * xb_node_export(XbNode *self, XbNodeExportFlags flags, GError **error) G_GNUC_NON_NULL(1); GBytes * xb_node_get_data(XbNode *self, const gchar *key) G_GNUC_NON_NULL(1, 2); void xb_node_set_data(XbNode *self, const gchar *key, GBytes *data) G_GNUC_NON_NULL(1, 2); XbNode * xb_node_get_root(XbNode *self) G_GNUC_NON_NULL(1); XbNode * xb_node_get_parent(XbNode *self) G_GNUC_NON_NULL(1); XbNode * xb_node_get_next(XbNode *self) G_GNUC_NON_NULL(1); XbNode * xb_node_get_child(XbNode *self) G_GNUC_NON_NULL(1); GPtrArray * xb_node_get_children(XbNode *self) G_GNUC_NON_NULL(1); const gchar * xb_node_get_element(XbNode *self) G_GNUC_NON_NULL(1); const gchar * xb_node_get_text(XbNode *self) G_GNUC_NON_NULL(1); guint64 xb_node_get_text_as_uint(XbNode *self) G_GNUC_NON_NULL(1); const gchar * xb_node_get_tail(XbNode *self) G_GNUC_NON_NULL(1); const gchar * xb_node_get_attr(XbNode *self, const gchar *name) G_GNUC_NON_NULL(1, 2); guint64 xb_node_get_attr_as_uint(XbNode *self, const gchar *name) G_GNUC_NON_NULL(1, 2); guint xb_node_get_depth(XbNode *self) G_GNUC_NON_NULL(1); void xb_node_attr_iter_init(XbNodeAttrIter *iter, XbNode *self) G_GNUC_NON_NULL(1, 2); gboolean xb_node_attr_iter_next(XbNodeAttrIter *iter, const gchar **name, const gchar **value) G_GNUC_NON_NULL(1); void xb_node_child_iter_init(XbNodeChildIter *iter, XbNode *self) G_GNUC_NON_NULL(1, 2); gboolean xb_node_child_iter_next(XbNodeChildIter *iter, XbNode **child) G_GNUC_NON_NULL(1, 2); gboolean xb_node_child_iter_loop(XbNodeChildIter *iter, XbNode **child) G_GNUC_NON_NULL(1, 2); G_END_DECLS libxmlb-0.3.22/src/xb-opcode-private.h000066400000000000000000000064521476425255200175420ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "xb-opcode.h" G_BEGIN_DECLS /* maximum number of tokens supported for each element -- this is a compromise * between making the _XbOpcode struct too large and search results */ #define XB_OPCODE_TOKEN_MAX 32 struct _XbOpcode { XbOpcodeKind kind; guint32 val; gpointer ptr; guint8 tokens_len; const gchar *tokens[XB_OPCODE_TOKEN_MAX + 1]; GDestroyNotify destroy_func; guint8 level; }; #define XB_OPCODE_INIT() {0, 0, NULL, 0, {NULL}, NULL, 0} /** * xb_opcode_steal: * @op_ptr: (transfer full): pointer to an #XbOpcode to steal * * Steal the stack-allocated #XbOpcode pointed to by @op_ptr, returning its * value and clearing its previous storage location using `memset()`. * * Returns: the value of @op_ptr * Since: 0.2.0 */ static inline XbOpcode xb_opcode_steal(XbOpcode *op_ptr) { XbOpcode op = *op_ptr; memset(op_ptr, 0, sizeof(XbOpcode)); return op; } void xb_opcode_init(XbOpcode *self, XbOpcodeKind kind, const gchar *str, guint32 val, GDestroyNotify destroy_func) G_GNUC_NON_NULL(1); void xb_opcode_clear(XbOpcode *self) G_GNUC_NON_NULL(1); void xb_opcode_bind_init(XbOpcode *self) G_GNUC_NON_NULL(1); gboolean xb_opcode_is_binding(XbOpcode *self) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(xb_value_bindings_bind_str) void xb_opcode_bind_str(XbOpcode *self, gchar *str, GDestroyNotify destroy_func) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(xb_value_bindings_bind_val) void xb_opcode_bind_val(XbOpcode *self, guint32 val) G_GNUC_NON_NULL(1); void xb_opcode_set_kind(XbOpcode *self, XbOpcodeKind kind) G_GNUC_NON_NULL(1); void xb_opcode_set_val(XbOpcode *self, guint32 val) G_GNUC_NON_NULL(1); gboolean xb_opcode_append_token(XbOpcode *self, const gchar *val) G_GNUC_NON_NULL(1, 2); const gchar ** xb_opcode_get_tokens(XbOpcode *self) G_GNUC_NON_NULL(1); gchar * xb_opcode_get_sig(XbOpcode *self) G_GNUC_NON_NULL(1); void xb_opcode_bool_init(XbOpcode *self, gboolean val) G_GNUC_NON_NULL(1); gboolean xb_opcode_has_flag(XbOpcode *self, XbOpcodeFlags flag) G_GNUC_NON_NULL(1); void xb_opcode_add_flag(XbOpcode *self, XbOpcodeFlags flag) G_GNUC_NON_NULL(1); void xb_opcode_set_level(XbOpcode *self, guint8 level) G_GNUC_NON_NULL(1); guint8 xb_opcode_get_level(XbOpcode *self) G_GNUC_NON_NULL(1); static inline gboolean _xb_opcode_has_flag(const XbOpcode *self, XbOpcodeFlags flag) { return (self->kind & flag) > 0; } static inline XbOpcodeKind _xb_opcode_get_kind(const XbOpcode *self) { return self->kind & ~XB_OPCODE_FLAG_TOKENIZED; } static inline const gchar * _xb_opcode_get_str(const XbOpcode *self) { return self->ptr; } static inline guint8 _xb_opcode_get_level(const XbOpcode *self) { return self->level; } static inline guint32 _xb_opcode_get_val(const XbOpcode *self) { return self->val; } static inline gboolean _xb_opcode_cmp_int(XbOpcode *self) { return self->kind == XB_OPCODE_KIND_INTEGER || self->kind == XB_OPCODE_KIND_BOOLEAN || self->kind == XB_OPCODE_KIND_BOUND_INTEGER; } static inline gboolean _xb_opcode_cmp_itx(XbOpcode *self) { return self->kind == XB_OPCODE_KIND_INDEXED_TEXT || self->kind == XB_OPCODE_KIND_BOUND_INDEXED_TEXT; } G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(XbOpcode, xb_opcode_clear) G_END_DECLS libxmlb-0.3.22/src/xb-opcode.c000066400000000000000000000261411476425255200160620ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbMachine" #include "config.h" #include #include "xb-opcode-private.h" /** * xb_opcode_kind_to_string: * @kind: a #XbOpcodeKind, e.g. %XB_OPCODE_KIND_FUNCTION * * Converts the opcode kind to a string. * * Returns: opcode kind, e.g. `FUNC` * * Since: 0.1.1 **/ const gchar * xb_opcode_kind_to_string(XbOpcodeKind kind) { /* special cases */ if (kind == XB_OPCODE_KIND_INTEGER) return "INTE"; if (kind == XB_OPCODE_KIND_BOUND_UNSET) return "BIND"; if (kind == XB_OPCODE_KIND_BOUND_TEXT) return "?TXT"; if (kind == XB_OPCODE_KIND_BOUND_INDEXED_TEXT) return "?ITX"; if (kind == XB_OPCODE_KIND_BOUND_INTEGER) return "?INT"; if (kind == XB_OPCODE_KIND_INDEXED_TEXT) return "TEXI"; if (kind == XB_OPCODE_KIND_BOOLEAN) return "BOOL"; /* bitwise fallbacks */ if (kind & XB_OPCODE_FLAG_FUNCTION) return "FUNC"; if (kind & XB_OPCODE_FLAG_TEXT) return "TEXT"; return NULL; } /** * xb_opcode_kind_from_string: * @str: a string, e.g. `FUNC` * * Converts a string to an opcode kind. * * Returns: a #XbOpcodeKind, e.g. %XB_OPCODE_KIND_TEXT * * Since: 0.1.1 **/ XbOpcodeKind xb_opcode_kind_from_string(const gchar *str) { if (g_strcmp0(str, "FUNC") == 0) return XB_OPCODE_KIND_FUNCTION; if (g_strcmp0(str, "TEXT") == 0) return XB_OPCODE_KIND_TEXT; if (g_strcmp0(str, "INTE") == 0) return XB_OPCODE_KIND_INTEGER; if (g_strcmp0(str, "BIND") == 0) return XB_OPCODE_KIND_BOUND_INTEGER; if (g_strcmp0(str, "?TXT") == 0) return XB_OPCODE_KIND_BOUND_TEXT; if (g_strcmp0(str, "?ITX") == 0) return XB_OPCODE_KIND_BOUND_INDEXED_TEXT; if (g_strcmp0(str, "?INT") == 0) return XB_OPCODE_KIND_BOUND_INTEGER; if (g_strcmp0(str, "TEXI") == 0) return XB_OPCODE_KIND_INDEXED_TEXT; if (g_strcmp0(str, "BOOL") == 0) return XB_OPCODE_KIND_BOOLEAN; return XB_OPCODE_KIND_UNKNOWN; } /* private */ gchar * xb_opcode_get_sig(XbOpcode *self) { GString *str = g_string_new(xb_opcode_kind_to_string(self->kind)); if (self->kind == XB_OPCODE_KIND_FUNCTION) { g_string_append_printf(str, ":%s", self->ptr != NULL ? (gchar *)self->ptr : "???"); } return g_string_free(str, FALSE); } static const gchar * xb_opcode_get_str_for_display(XbOpcode *self) { if (self->ptr == NULL) return "(null)"; return self->ptr; } void xb_opcode_set_level(XbOpcode *self, guint8 level) { self->level = level; } static gchar * xb_opcode_to_string_internal(XbOpcode *self) { g_autoptr(GString) str = g_string_new(NULL); /* special cases then bitwise fallbacks */ if (self->kind == XB_OPCODE_KIND_INDEXED_TEXT) g_string_append_printf(str, "$'%s'", xb_opcode_get_str_for_display(self)); else if (self->kind == XB_OPCODE_KIND_INTEGER) g_string_append_printf(str, "%u", xb_opcode_get_val(self)); else if (self->kind == XB_OPCODE_KIND_BOUND_TEXT || self->kind == XB_OPCODE_KIND_BOUND_INDEXED_TEXT) g_string_append_printf(str, "?'%s'", xb_opcode_get_str_for_display(self)); else if (self->kind == XB_OPCODE_KIND_BOUND_INTEGER) g_string_append_printf(str, "?%u", xb_opcode_get_val(self)); else if (self->kind == XB_OPCODE_KIND_BOOLEAN) return g_strdup(xb_opcode_get_val(self) ? "True" : "False"); else if (self->kind & XB_OPCODE_FLAG_FUNCTION) g_string_append_printf(str, "%s()", xb_opcode_get_str_for_display(self)); else if (self->kind & XB_OPCODE_FLAG_TEXT) g_string_append_printf(str, "'%s'", xb_opcode_get_str_for_display(self)); else g_string_append_printf(str, "kind:0x%x", self->kind); /* add level */ if (self->level > 0) g_string_append_printf(str, "^%u", self->level); return g_string_free(g_steal_pointer(&str), FALSE); } /** * xb_opcode_to_string: * @self: a #XbOpcode * * Returns a string representing the specific opcode. * * Returns: text * * Since: 0.1.4 **/ gchar * xb_opcode_to_string(XbOpcode *self) { g_autofree gchar *tmp = xb_opcode_to_string_internal(self); if (self->kind & XB_OPCODE_FLAG_TOKENIZED) { g_autofree gchar *tokens = NULL; /* Ensure the array is NULL-terminated */ self->tokens[self->tokens_len] = NULL; tokens = g_strjoinv(",", (gchar **)self->tokens); return g_strdup_printf("%s[%s]", tmp, tokens); } return g_steal_pointer(&tmp); } /** * xb_opcode_get_kind: * @self: a #XbOpcode * * Gets the opcode kind. * * Returns: a #XbOpcodeKind, e.g. %XB_OPCODE_KIND_INTEGER * * Since: 0.1.1 **/ XbOpcodeKind xb_opcode_get_kind(XbOpcode *self) { return self->kind & ~XB_OPCODE_FLAG_TOKENIZED; } /** * xb_opcode_has_flag: * @self: a #XbOpcode * @flag: a #XbOpcodeFlags, e.g. #XB_OPCODE_FLAG_TOKENIZED * * Finds out if an opcode has a flag set. * * Returns: %TRUE if the flag is set * * Since: 0.3.1 **/ gboolean xb_opcode_has_flag(XbOpcode *self, XbOpcodeFlags flag) { return (self->kind & flag) > 0; } /** * xb_opcode_add_flag: * @self: a #XbOpcode * @flag: a #XbOpcodeFlags, e.g. #XB_OPCODE_FLAG_TOKENIZED * * Adds a flag to the opcode. * * Since: 0.3.1 **/ void xb_opcode_add_flag(XbOpcode *self, XbOpcodeFlags flag) { self->kind |= flag; } /** * xb_opcode_cmp_val: * @self: a #XbOpcode * * Checks if the opcode can be compared using the integer value. * * Returns: #%TRUE if this opcode can be compared as an integer * * Since: 0.1.1 **/ gboolean xb_opcode_cmp_val(XbOpcode *self) { return _xb_opcode_cmp_int(self) || _xb_opcode_cmp_itx(self); } /** * xb_opcode_cmp_str: * @self: a #XbOpcode * * Checks if the opcode can be compared using the string value. * * Returns: #%TRUE if this opcode can be compared as an string * * Since: 0.1.1 **/ gboolean xb_opcode_cmp_str(XbOpcode *self) { return xb_opcode_has_flag(self, XB_OPCODE_FLAG_TEXT); } /* private */ gboolean xb_opcode_is_binding(XbOpcode *self) { return xb_opcode_has_flag(self, XB_OPCODE_FLAG_BOUND); } /** * xb_opcode_get_val: * @self: a #XbOpcode * * Gets the integer value stored in the opcode. This may be a function ID, * a index into the string table or a literal integer. * * Returns: value, or 0 for unset. * * Since: 0.1.1 **/ guint32 xb_opcode_get_val(XbOpcode *self) { return self->val; } /** * xb_opcode_get_str: * @self: a #XbOpcode * * Gets the string value stored on the opcode. * * Returns: a string, or %NULL if unset * * Since: 0.1.1 **/ const gchar * xb_opcode_get_str(XbOpcode *self) { return _xb_opcode_get_str(self); } /** * xb_opcode_get_tokens: * @self: a #XbOpcode * * Gets the tokenized string stored on the opcode. * * Returns: a #GStrv, which is always %NULL terminated * * Since: 0.3.1 **/ const gchar ** xb_opcode_get_tokens(XbOpcode *self) { return self->tokens; } /** * xb_opcode_clear: * @self: a #XbOpcode * * Clears any allocated data inside the opcode, but does not free the #XbOpcode * itself. This is suitable for calling on stack-allocated #XbOpcodes. * * Since: 0.2.0 */ void xb_opcode_clear(XbOpcode *self) { if (self->destroy_func) self->destroy_func(self->ptr); self->destroy_func = NULL; } /** * xb_opcode_text_init: * @self: a stack allocated #XbOpcode to initialise * @str: a string * * Initialises a stack allocated #XbOpcode to contain a text literal. * The @str argument is copied internally and is not tied to the lifecycle of * the #XbOpcode. * * Since: 0.2.0 **/ void xb_opcode_text_init(XbOpcode *self, const gchar *str) { xb_opcode_init(self, XB_OPCODE_KIND_TEXT, g_strdup(str), 0, g_free); } /** * xb_opcode_init: * @self: allocated opcode to fill * @kind: a #XbOpcodeKind, e.g. %XB_OPCODE_KIND_INTEGER * @str: a string * @val: a integer value * @destroy_func: (nullable): a #GDestroyNotify, e.g. g_free() * * Initialises a stack allocated #XbOpcode. * * Since: 0.2.0 **/ void xb_opcode_init(XbOpcode *self, XbOpcodeKind kind, const gchar *str, guint32 val, GDestroyNotify destroy_func) { self->level = G_MAXUINT8; self->kind = kind; self->ptr = (gpointer)str; self->val = val; self->tokens_len = 0; self->destroy_func = destroy_func; memset(self->tokens, 0, sizeof(self->tokens)); } /** * xb_opcode_text_init_static: * @self: a stack allocated #XbOpcode to initialise * @str: a string * * Initialises a stack allocated #XbOpcode to contain a text literal, where * @str is either static text or will outlive the #XbOpcode lifecycle. * * Since: 0.2.0 **/ void xb_opcode_text_init_static(XbOpcode *self, const gchar *str) { xb_opcode_init(self, XB_OPCODE_KIND_TEXT, str, 0, NULL); } /** * xb_opcode_text_init_steal: * @self: a stack allocated #XbOpcode to initialise * @str: a string * * Initialises a stack allocated #XbOpcode to contain a text literal, stealing * the @str. Once the opcode is finalized g_free() will be called on @str. * * Since: 0.2.0 **/ void xb_opcode_text_init_steal(XbOpcode *self, gchar *str) { xb_opcode_init(self, XB_OPCODE_KIND_TEXT, g_steal_pointer(&str), 0, g_free); } /** * xb_opcode_func_init: * @self: a stack allocated #XbOpcode to initialise * @func: a function index * * Initialises a stack allocated #XbOpcode to contain a specific function. * Custom functions can be registered using xb_machine_add_func() and retrieved * using xb_machine_opcode_func_new(). * * Since: 0.2.0 **/ void xb_opcode_func_init(XbOpcode *self, guint32 func) { xb_opcode_init(self, XB_OPCODE_KIND_FUNCTION, NULL, func, NULL); } /** * xb_opcode_bind_init: * @self: a stack allocated #XbOpcode to initialise * * Initialises a stack allocated #XbOpcode to contain a bind variable. A value * needs to be assigned to this opcode at runtime using * xb_value_bindings_bind_str() or xb_value_bindings_bind_val(). * * Since: 0.2.0 **/ void xb_opcode_bind_init(XbOpcode *self) { xb_opcode_init(self, XB_OPCODE_KIND_BOUND_INTEGER, NULL, 0, NULL); } /* private */ void xb_opcode_bind_str(XbOpcode *self, gchar *str, GDestroyNotify destroy_func) { if (self->destroy_func) { self->destroy_func(self->ptr); self->destroy_func = NULL; } self->kind = XB_OPCODE_KIND_BOUND_TEXT; self->ptr = (gpointer)str; self->destroy_func = (gpointer)destroy_func; } /* private */ void xb_opcode_bind_val(XbOpcode *self, guint32 val) { if (self->destroy_func) { self->destroy_func(self->ptr); self->destroy_func = NULL; } self->kind = XB_OPCODE_KIND_BOUND_INTEGER; self->val = val; } /* private */ void xb_opcode_set_val(XbOpcode *self, guint32 val) { self->val = val; } /* private */ gboolean xb_opcode_append_token(XbOpcode *self, const gchar *val) { g_return_val_if_fail(val != NULL, FALSE); g_return_val_if_fail(val[0] != '\0', FALSE); if (self->tokens_len >= XB_OPCODE_TOKEN_MAX) return FALSE; self->tokens[self->tokens_len++] = val; self->kind |= XB_OPCODE_FLAG_TOKENIZED; return TRUE; } /* private */ void xb_opcode_set_kind(XbOpcode *self, XbOpcodeKind kind) { self->kind = kind; } /** * xb_opcode_integer_init: * @self: a stack allocated #XbOpcode to initialise * @val: a integer value * * Initialises a stack allocated #XbOpcode to contain an integer literal. * * Since: 0.2.0 **/ void xb_opcode_integer_init(XbOpcode *self, guint32 val) { xb_opcode_init(self, XB_OPCODE_KIND_INTEGER, NULL, val, NULL); } /* private */ void xb_opcode_bool_init(XbOpcode *self, gboolean val) { xb_opcode_init(self, XB_OPCODE_KIND_BOOLEAN, NULL, !!val, NULL); } libxmlb-0.3.22/src/xb-opcode.h000066400000000000000000000067561476425255200161010ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-compile.h" G_BEGIN_DECLS /** * XbOpcodeFlags: * @XB_OPCODE_FLAG_NONE: No flags set * @XB_OPCODE_FLAG_INTEGER: Integer value set * @XB_OPCODE_FLAG_TEXT: Text value set * @XB_OPCODE_FLAG_FUNCTION: An operator * @XB_OPCODE_FLAG_BOUND: A bound value, assigned later * @XB_OPCODE_FLAG_TOKENIZED: Tokenized text * * The opcode flags. The values have been carefully chosen so that a simple * bitmask can be done to know how to compare for equality. * * function─┐ ┌─string * bound──┐ │ │ ┌──integer * token┐ │ │ │ │ * X X X X X X X * 8 4 2 1 **/ typedef enum { XB_OPCODE_FLAG_UNKNOWN = 0x0, /* Since: 0.1.4 */ XB_OPCODE_FLAG_INTEGER = 1 << 0, /* Since: 0.1.4 */ XB_OPCODE_FLAG_TEXT = 1 << 1, /* Since: 0.1.4 */ XB_OPCODE_FLAG_FUNCTION = 1 << 2, /* Since: 0.1.4 */ XB_OPCODE_FLAG_BOUND = 1 << 3, /* Since: 0.1.4 */ XB_OPCODE_FLAG_BOOLEAN = 1 << 4, /* Since: 0.1.11 */ XB_OPCODE_FLAG_TOKENIZED = 1 << 5, /* Since: 0.3.1 */ /*< private >*/ XB_OPCODE_FLAG_LAST } XbOpcodeFlags; /** * XbOpcodeKind: * @XB_OPCODE_KIND_UNKNOWN: Unknown opcode * @XB_OPCODE_KIND_INTEGER: A literal integer value * @XB_OPCODE_KIND_TEXT: A literal text value * @XB_OPCODE_KIND_FUNCTION: An operator * @XB_OPCODE_KIND_BOUND_INTEGER: A bound integer value * @XB_OPCODE_KIND_BOUND_TEXT: A bound text value * @XB_OPCODE_KIND_INDEXED_TEXT: An indexed text value * @XB_OPCODE_KIND_BOUND_INDEXED_TEXT: An bound indexed text value **/ typedef enum { XB_OPCODE_KIND_UNKNOWN = 0x0, /* Since: 0.1.1 */ XB_OPCODE_KIND_INTEGER = XB_OPCODE_FLAG_INTEGER, /* Since: 0.1.1 */ XB_OPCODE_KIND_TEXT = XB_OPCODE_FLAG_TEXT, /* Since: 0.1.1 */ XB_OPCODE_KIND_FUNCTION = XB_OPCODE_FLAG_FUNCTION | XB_OPCODE_FLAG_INTEGER, /* Since: 0.1.1 */ XB_OPCODE_KIND_BOUND_UNSET = XB_OPCODE_FLAG_BOUND, /* Since: 0.1.4 */ XB_OPCODE_KIND_BOUND_INTEGER = XB_OPCODE_FLAG_BOUND | XB_OPCODE_FLAG_INTEGER, /* Since: 0.1.4 */ XB_OPCODE_KIND_BOUND_TEXT = XB_OPCODE_FLAG_BOUND | XB_OPCODE_FLAG_TEXT, /* Since: 0.1.4 */ XB_OPCODE_KIND_INDEXED_TEXT = XB_OPCODE_FLAG_INTEGER | XB_OPCODE_FLAG_TEXT, /* Since: 0.1.4 */ XB_OPCODE_KIND_BOOLEAN = XB_OPCODE_FLAG_INTEGER | XB_OPCODE_FLAG_BOOLEAN, /* Since: 0.1.11 */ XB_OPCODE_KIND_BOUND_INDEXED_TEXT = XB_OPCODE_FLAG_BOUND | XB_OPCODE_FLAG_INTEGER | XB_OPCODE_FLAG_TEXT, /* Since: 0.3.12 */ /*< private >*/ XB_OPCODE_KIND_LAST } XbOpcodeKind; typedef struct _XbOpcode XbOpcode; gboolean xb_opcode_cmp_val(XbOpcode *self) G_GNUC_NON_NULL(1); gboolean xb_opcode_cmp_str(XbOpcode *self) G_GNUC_NON_NULL(1); gchar * xb_opcode_to_string(XbOpcode *self) G_GNUC_NON_NULL(1); const gchar * xb_opcode_kind_to_string(XbOpcodeKind kind); XbOpcodeKind xb_opcode_kind_from_string(const gchar *str); XbOpcodeKind xb_opcode_get_kind(XbOpcode *self) G_GNUC_NON_NULL(1); const gchar * xb_opcode_get_str(XbOpcode *self) G_GNUC_NON_NULL(1); guint32 xb_opcode_get_val(XbOpcode *self) G_GNUC_NON_NULL(1); void xb_opcode_func_init(XbOpcode *self, guint32 func) G_GNUC_NON_NULL(1); void xb_opcode_integer_init(XbOpcode *self, guint32 val) G_GNUC_NON_NULL(1); void xb_opcode_text_init(XbOpcode *self, const gchar *str) G_GNUC_NON_NULL(1); void xb_opcode_text_init_static(XbOpcode *self, const gchar *str) G_GNUC_NON_NULL(1); void xb_opcode_text_init_steal(XbOpcode *self, gchar *str) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-query-context.c000066400000000000000000000122651476425255200174420ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * vi:set noexpandtab tabstop=8 shiftwidth=8: * * Copyright 2020 Endless OS Foundation LLC * * Author: Philip Withnall * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbQueryContext" #include "config.h" #include #include "xb-query-context.h" #include "xb-query.h" #include "xb-value-bindings.h" /* Why are #XbQueryContext and #XbValueBindings not the same object? * #XbQueryContext is associated with a query, but the #XbValueBindings is * associated with a query *and* the #XbMachine which runs for it. Once an * #XbQuery is turned into an #XbMachine to be evaluated, the #XbQueryContext is * ignored and only the #XbValueBindings are taken forward to be used, copied * and subsetted for various parts of the #XbMachine. */ typedef struct { guint limit; XbQueryFlags flags; XbValueBindings bindings; gpointer dummy[5]; } RealQueryContext; G_STATIC_ASSERT(sizeof(XbQueryContext) == sizeof(RealQueryContext)); G_DEFINE_BOXED_TYPE(XbQueryContext, xb_query_context, xb_query_context_copy, xb_query_context_free) /** * xb_query_context_init: * @self: an uninitialised #XbQueryContext to initialise * * Initialise a stack-allocated #XbQueryContext struct so it can be used. * * Stack-allocated #XbQueryContext instances should be freed once finished * with, using xb_query_context_clear() (or `g_auto(XbQueryContext)`, which is * equivalent). * * Since: 0.3.0 */ void xb_query_context_init(XbQueryContext *self) { RealQueryContext *_self = (RealQueryContext *)self; _self->limit = 0; _self->flags = XB_QUERY_FLAG_NONE; xb_value_bindings_init(&_self->bindings); } /** * xb_query_context_clear: * @self: an #XbQueryContext * * Clear an #XbQueryContext, freeing any allocated memory it points to. * * After this function has been called, the contents of the #XbQueryContext are * undefined, and it’s only safe to call xb_query_context_init() on it. * * Since: 0.3.0 */ void xb_query_context_clear(XbQueryContext *self) { RealQueryContext *_self = (RealQueryContext *)self; xb_value_bindings_clear(&_self->bindings); } /** * xb_query_context_copy: * @self: an #XbQueryContext * * Copy @self into a new heap-allocated #XbQueryContext instance. * * Returns: (transfer full): a copy of @self * Since: 0.3.0 */ XbQueryContext * xb_query_context_copy(XbQueryContext *self) { RealQueryContext *_self = (RealQueryContext *)self; g_autoptr(XbQueryContext) copy = g_new0(XbQueryContext, 1); RealQueryContext *_copy = (RealQueryContext *)copy; gsize i = 0; xb_query_context_init(copy); _copy->limit = _self->limit; _copy->flags = _self->flags; while (xb_value_bindings_copy_binding(&_self->bindings, i, &_copy->bindings, i)) i++; return g_steal_pointer(©); } /** * xb_query_context_free: * @self: a heap-allocated #XbQueryContext * * Free a heap-allocated #XbQueryContext instance. This should be used on * #XbQueryContext instances created with xb_query_context_copy(). * * For stack-allocated instances, xb_query_context_clear() should be used * instead. * * Since: 0.3.0 */ void xb_query_context_free(XbQueryContext *self) { g_return_if_fail(self != NULL); xb_query_context_clear(self); g_free(self); } /** * xb_query_context_get_bindings: * @self: an #XbQueryContext * * Get the #XbValueBindings for this query context. * * Returns: (transfer none) (not nullable): bindings * Since: 0.3.0 */ XbValueBindings * xb_query_context_get_bindings(XbQueryContext *self) { RealQueryContext *_self = (RealQueryContext *)self; g_return_val_if_fail(self != NULL, NULL); return &_self->bindings; } /** * xb_query_context_get_limit: * @self: an #XbQueryContext * * Get the limit on the number of query results. See * xb_query_context_set_limit(). * * Returns: limit on results, or `0` if unlimited * Since: 0.3.0 */ guint xb_query_context_get_limit(XbQueryContext *self) { RealQueryContext *_self = (RealQueryContext *)self; g_return_val_if_fail(self != NULL, 0); return _self->limit; } /** * xb_query_context_set_limit: * @self: an #XbQueryContext * @limit: number of query results to return, or `0` for unlimited * * Set the limit on the number of results to return from the query. * * Since: 0.3.0 */ void xb_query_context_set_limit(XbQueryContext *self, guint limit) { RealQueryContext *_self = (RealQueryContext *)self; g_return_if_fail(self != NULL); _self->limit = limit; } /** * xb_query_context_get_flags: * @self: an #XbQueryContext * * Get the flags set on the context. See xb_query_context_set_flags(). * * Returns: query flags * Since: 0.3.0 */ XbQueryFlags xb_query_context_get_flags(XbQueryContext *self) { RealQueryContext *_self = (RealQueryContext *)self; g_return_val_if_fail(self != NULL, XB_QUERY_FLAG_NONE); return _self->flags; } /** * xb_query_context_set_flags: * @self: an #XbQueryContext * @flags: query flags, or %XB_QUERY_FLAG_NONE for none * * Set flags which affect the behaviour of the query. * * Since: 0.3.0 */ void xb_query_context_set_flags(XbQueryContext *self, XbQueryFlags flags) { RealQueryContext *_self = (RealQueryContext *)self; g_return_if_fail(self != NULL); _self->flags = flags; } libxmlb-0.3.22/src/xb-query-context.h000066400000000000000000000047241476425255200174500ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * vi:set noexpandtab tabstop=8 shiftwidth=8: * * Copyright 2020 Endless OS Foundation LLC * * Author: Philip Withnall * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "xb-query.h" #include "xb-value-bindings.h" G_BEGIN_DECLS /** * XbQueryContext: * * An opaque struct which contains context for executing a query in, such as the * number of results to return, or values to bind to query placeholders. * * Since: 0.3.0 */ typedef struct { /*< private >*/ gint dummy0; guint dummy1; XbValueBindings dummy2; gpointer dummy3[5]; } XbQueryContext; GType xb_query_context_get_type(void); /** * XB_QUERY_CONTEXT_INIT: * * Static initialiser for #XbQueryContext so it can be used on the stack. * * Use it in association with g_auto(), to ensure the bindings are freed once * finished with: * |[ * g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT (); * * xb_query_context_set_limit (&context, 0); * ]| * * Since: 0.3.0 */ #define XB_QUERY_CONTEXT_INIT() \ { \ 0, 0, XB_VALUE_BINDINGS_INIT(), \ { \ NULL, NULL, NULL, NULL, NULL \ } \ } void xb_query_context_init(XbQueryContext *self) G_GNUC_NON_NULL(1); void xb_query_context_clear(XbQueryContext *self) G_GNUC_NON_NULL(1); G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(XbQueryContext, xb_query_context_clear) XbQueryContext * xb_query_context_copy(XbQueryContext *self) G_GNUC_NON_NULL(1); void xb_query_context_free(XbQueryContext *self) G_GNUC_NON_NULL(1); G_DEFINE_AUTOPTR_CLEANUP_FUNC(XbQueryContext, xb_query_context_free) XbValueBindings * xb_query_context_get_bindings(XbQueryContext *self) G_GNUC_NON_NULL(1); guint xb_query_context_get_limit(XbQueryContext *self) G_GNUC_NON_NULL(1); void xb_query_context_set_limit(XbQueryContext *self, guint limit) G_GNUC_NON_NULL(1); XbQueryFlags xb_query_context_get_flags(XbQueryContext *self) G_GNUC_NON_NULL(1); void xb_query_context_set_flags(XbQueryContext *self, XbQueryFlags flags) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-query-private.h000066400000000000000000000011541476425255200174300ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "xb-query.h" G_BEGIN_DECLS typedef enum { XB_SILO_QUERY_KIND_UNKNOWN, XB_SILO_QUERY_KIND_WILDCARD, XB_SILO_QUERY_KIND_PARENT, XB_SILO_QUERY_KIND_LAST } XbSiloQueryKind; typedef struct { gchar *element; guint32 element_idx; GPtrArray *predicates; /* of XbStack */ XbSiloQueryKind kind; } XbQuerySection; GPtrArray * xb_query_get_sections(XbQuery *self) G_GNUC_NON_NULL(1); gchar * xb_query_to_string(XbQuery *self) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-query.c000066400000000000000000000346371476425255200157670ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbSilo" #include "config.h" #include #include "xb-machine.h" #include "xb-opcode-private.h" #include "xb-query-private.h" #include "xb-silo-private.h" #include "xb-stack-private.h" typedef struct { GPtrArray *sections; /* of XbQuerySection */ XbQueryFlags flags; gchar *xpath; guint limit; } XbQueryPrivate; G_DEFINE_TYPE_WITH_PRIVATE(XbQuery, xb_query, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_query_get_instance_private(o)) typedef struct { XbSilo *silo; } XbQueryParseContext; /** * xb_query_get_sections: * @self: a #XbQuery * * Gets the sections that make up the query. * * Returns: (transfer none) (element-type XbQuerySection): sections * * Since: 0.1.4 **/ GPtrArray * xb_query_get_sections(XbQuery *self) { XbQueryPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_QUERY(self), NULL); return priv->sections; } /** * xb_query_get_xpath: * @self: a #XbQuery * * Gets the XPath string that created the query. * * Returns: string * * Since: 0.1.4 **/ const gchar * xb_query_get_xpath(XbQuery *self) { XbQueryPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_QUERY(self), NULL); return priv->xpath; } static gchar * xb_query_section_to_string(XbQuerySection *sect) { GString *str = g_string_new(NULL); if (sect->kind == XB_SILO_QUERY_KIND_PARENT) g_string_append(str, ".."); else if (sect->kind == XB_SILO_QUERY_KIND_WILDCARD) g_string_append(str, "*"); else g_string_append(str, sect->element); if (sect->predicates != NULL && sect->predicates->len > 0) { g_string_append(str, "["); for (guint j = 0; j < sect->predicates->len; j++) { XbStack *stack = g_ptr_array_index(sect->predicates, j); g_autofree gchar *tmp = xb_stack_to_string(stack); g_string_append(str, tmp); } g_string_append(str, "]"); } return g_string_free(str, FALSE); } /** * xb_query_to_string: * @self: a #XbQuery * * Gets the XPath that was used for the query. * * Returns: string * * Since: 0.1.13 **/ gchar * xb_query_to_string(XbQuery *self) { XbQueryPrivate *priv = GET_PRIVATE(self); GString *str = g_string_new(NULL); for (guint i = 0; i < priv->sections->len; i++) { XbQuerySection *sect = g_ptr_array_index(priv->sections, i); g_autofree gchar *tmp = xb_query_section_to_string(sect); g_string_append(str, tmp); if (i != priv->sections->len - 1) g_string_append(str, "/"); } return g_string_free(str, FALSE); } /** * xb_query_get_limit: * @self: a #XbQuery * * Gets the results limit on this query, where 0 is 'all'. * * Returns: integer, default 0 * * Deprecated: 0.3.0: This is not thread-safe. Use xb_query_context_get_limit() * instead. * Since: 0.1.4 **/ guint xb_query_get_limit(XbQuery *self) { XbQueryPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_QUERY(self), 0); return priv->limit; } /** * xb_query_set_limit: * @self: a #XbQuery * @limit: integer * * Sets the results limit on this query, where 0 is 'all'. * * Deprecated: 0.3.0: This is not thread-safe. Use xb_query_context_set_limit() * instead. * Since: 0.1.4 **/ void xb_query_set_limit(XbQuery *self, guint limit) { XbQueryPrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_QUERY(self)); priv->limit = limit; } /** * xb_query_get_flags: * @self: a #XbQuery * * Gets the flags used for this query. * * Returns: #XbQueryFlags, default %XB_QUERY_FLAG_NONE * * Deprecated: 0.3.0: This is not thread-safe. Use xb_query_context_get_flags() * instead. * Since: 0.1.15 **/ XbQueryFlags xb_query_get_flags(XbQuery *self) { XbQueryPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_QUERY(self), 0); return priv->flags; } /** * xb_query_set_flags: * @self: a #XbQuery * @flags: a #XbQueryFlags, e.g. %XB_QUERY_FLAG_USE_INDEXES * * Sets the flags to use for this query. * * Deprecated: 0.3.0: This is not thread-safe. Use xb_query_context_set_flags() * instead. * Since: 0.1.15 **/ void xb_query_set_flags(XbQuery *self, XbQueryFlags flags) { XbQueryPrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_QUERY(self)); priv->flags = flags; } static XbOpcode * xb_query_get_bound_opcode(XbQuery *self, guint idx) { XbQueryPrivate *priv = GET_PRIVATE(self); guint idx_cnt = 0; for (guint i = 0; i < priv->sections->len; i++) { XbQuerySection *section = g_ptr_array_index(priv->sections, i); if (section->predicates == NULL) continue; for (guint j = 0; j < section->predicates->len; j++) { XbStack *stack = g_ptr_array_index(section->predicates, j); for (guint k = 0; k < xb_stack_get_size(stack); k++) { XbOpcode *op = xb_stack_peek(stack, k); if (xb_opcode_is_binding(op)) { if (idx == idx_cnt++) return op; } } } } return NULL; } /** * xb_query_bind_str: * @self: a #XbQuery * @idx: an integer index * @str: string to assign to the bound variable * @error: a #GError, or %NULL * * Assigns a string to a bound value specified using `?`. * * Returns: %TRUE if the @idx existed * * Since: 0.1.4 * Deprecated: 0.3.0: Use #XbValueBindings and xb_value_bindings_bind_str() * instead. That keeps the value bindings separate from the #XbQuery, * allowing queries to be re-used over time and between threads. **/ gboolean xb_query_bind_str(XbQuery *self, guint idx, const gchar *str, GError **error) { G_GNUC_BEGIN_IGNORE_DEPRECATIONS XbOpcode *op; g_return_val_if_fail(XB_IS_QUERY(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* get the correct opcode */ op = xb_query_get_bound_opcode(self, idx); if (op == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no bound opcode with index %u", idx); return FALSE; } xb_opcode_bind_str(op, g_strdup(str), g_free); return TRUE; G_GNUC_END_IGNORE_DEPRECATIONS } /** * xb_query_bind_val: * @self: a #XbQuery * @idx: an integer index * @val: value to assign to the bound variable * @error: a #GError, or %NULL * * Assigns a string to a bound value specified using `?`. * * Returns: %TRUE if the @idx existed * * Since: 0.1.4 * Deprecated: 0.3.0: Use #XbValueBindings and xb_value_bindings_bind_val() * instead. That keeps the value bindings separate from the #XbQuery, * allowing queries to be re-used over time and between threads. **/ gboolean xb_query_bind_val(XbQuery *self, guint idx, guint32 val, GError **error) { G_GNUC_BEGIN_IGNORE_DEPRECATIONS XbOpcode *op; g_return_val_if_fail(XB_IS_QUERY(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* get the correct opcode */ op = xb_query_get_bound_opcode(self, idx); if (op == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no bound opcode with index %u", idx); return FALSE; } xb_opcode_bind_val(op, val); return TRUE; G_GNUC_END_IGNORE_DEPRECATIONS } static void xb_query_section_free(XbQuerySection *section) { if (section->predicates != NULL) g_ptr_array_unref(section->predicates); g_free(section->element); g_slice_free(XbQuerySection, section); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(XbQuerySection, xb_query_section_free) static gboolean xb_query_repair_opcode_texi(XbQuery *self, XbQueryParseContext *context, XbOpcode *op, GError **error) { if (xb_opcode_get_val(op) == XB_SILO_UNSET) { const gchar *tmp = xb_opcode_get_str(op); guint32 val = xb_silo_strtab_index_lookup(context->silo, tmp); if (val == XB_SILO_UNSET) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "indexed string '%s' was unfound", tmp); return FALSE; } xb_opcode_set_val(op, val); } return TRUE; } /* Returns an error if the XPath is invalid. */ static gboolean xb_query_parse_predicate(XbQuery *self, XbQueryParseContext *context, XbQuerySection *section, const gchar *text, gssize text_len, GError **error) { XbQueryPrivate *priv = GET_PRIVATE(self); XbMachineParseFlags machine_flags = XB_MACHINE_PARSE_FLAG_NONE; g_autoptr(XbStack) opcodes = NULL; /* set flags */ if (priv->flags & XB_QUERY_FLAG_OPTIMIZE) machine_flags |= XB_MACHINE_PARSE_FLAG_OPTIMIZE; /* parse */ opcodes = xb_machine_parse_full(xb_silo_get_machine(context->silo), text, text_len, machine_flags, error); if (opcodes == NULL) return FALSE; /* repair or convert the indexed strings */ if (priv->flags & XB_QUERY_FLAG_USE_INDEXES) { for (guint i = 0; i < xb_stack_get_size(opcodes); i++) { XbOpcode *op = xb_stack_peek(opcodes, i); if (xb_opcode_get_kind(op) != XB_OPCODE_KIND_INDEXED_TEXT) continue; if (!xb_query_repair_opcode_texi(self, context, op, error)) return FALSE; } } else { for (guint i = 0; i < xb_stack_get_size(opcodes); i++) { XbOpcode *op = xb_stack_peek(opcodes, i); if (xb_opcode_get_kind(op) == XB_OPCODE_KIND_INDEXED_TEXT) xb_opcode_set_kind(op, XB_OPCODE_KIND_TEXT); } } /* create array if it does not exist */ if (section->predicates == NULL) section->predicates = g_ptr_array_new_with_free_func((GDestroyNotify)xb_stack_unref); g_ptr_array_add(section->predicates, g_steal_pointer(&opcodes)); return TRUE; } /* Returns an error if the XPath is invalid. */ static XbQuerySection * xb_query_parse_section(XbQuery *self, XbQueryParseContext *context, const gchar *xpath, GError **error) { g_autoptr(XbQuerySection) section = g_slice_new0(XbQuerySection); guint start = 0; /* common XPath sections */ if (g_strcmp0(xpath, "parent::*") == 0 || g_strcmp0(xpath, "..") == 0) { section->kind = XB_SILO_QUERY_KIND_PARENT; return g_steal_pointer(§ion); } /* parse element and predicate */ for (guint i = 0; xpath[i] != '\0'; i++) { if (start == 0 && xpath[i] == '[') { if (section->element == NULL) section->element = g_strndup(xpath, i); start = i; continue; } if (start > 0 && xpath[i] == ']') { if (!xb_query_parse_predicate(self, context, section, xpath + start + 1, i - start - 1, error)) { return NULL; } start = 0; continue; } } /* incomplete predicate */ if (start != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "predicate %s was unfinished, missing ']'", xpath + start); return NULL; } if (section->element == NULL) section->element = g_strdup(xpath); if (g_strcmp0(section->element, "child::*") == 0 || g_strcmp0(section->element, "*") == 0) { section->kind = XB_SILO_QUERY_KIND_WILDCARD; return g_steal_pointer(§ion); } /* This may result in @element_idx being set to %XB_SILO_UNSET if the * given element (`section->element`) is not in the silo at all. Ignore * that for now, and return no matches when the query is actually run. */ section->element_idx = xb_silo_get_strtab_idx(context->silo, section->element); return g_steal_pointer(§ion); } /* Returns an error if the XPath is invalid. */ static gboolean xb_query_parse(XbQuery *self, XbQueryParseContext *context, const gchar *xpath, GError **error) { XbQueryPrivate *priv = GET_PRIVATE(self); XbQuerySection *section; g_autoptr(GString) acc = g_string_new(NULL); // g_debug ("parsing XPath %s", xpath); for (gsize i = 0; xpath[i] != '\0'; i++) { /* escaped chars */ if (xpath[i] == '\\') { if (xpath[i + 1] == '/' || xpath[i + 1] == 't' || xpath[i + 1] == 'n') { g_string_append_c(acc, xpath[i + 1]); i += 1; continue; } } /* split */ if (xpath[i] == '/') { if (acc->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "xpath section empty"); return FALSE; } section = xb_query_parse_section(self, context, acc->str, error); if (section == NULL) return FALSE; g_ptr_array_add(priv->sections, section); g_string_truncate(acc, 0); continue; } g_string_append_c(acc, xpath[i]); } /* add any remaining section */ section = xb_query_parse_section(self, context, acc->str, error); if (section == NULL) return FALSE; g_ptr_array_add(priv->sections, section); return TRUE; } /** * xb_query_new_full: * @silo: a #XbSilo * @xpath: The XPath query * @flags: some #XbQueryFlags, e.g. #XB_QUERY_FLAG_USE_INDEXES * @error: the #GError, or %NULL * * Creates a query to be used by @silo. It may be quicker to create a query * manually and re-use it multiple times. * * The query will point to strings inside @silo, so the lifetime of @silo must * exceed the lifetime of the returned query. * * Returns: (transfer full): a #XbQuery * * Since: 0.1.6 **/ XbQuery * xb_query_new_full(XbSilo *silo, const gchar *xpath, XbQueryFlags flags, GError **error) { g_autoptr(XbQuery) self = g_object_new(XB_TYPE_QUERY, NULL); XbQueryPrivate *priv = GET_PRIVATE(self); XbQueryParseContext parse_context = { .silo = silo, }; g_return_val_if_fail(XB_IS_SILO(silo), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* create; don’t take a reference on @silo otherwise we get refcount * loops with cached queries from xb_silo_lookup_query() */ priv->xpath = g_strdup(xpath); priv->flags = flags; priv->sections = g_ptr_array_new_with_free_func((GDestroyNotify)xb_query_section_free); /* add each section */ if (!xb_query_parse(self, &parse_context, xpath, error)) return NULL; /* nothing here! */ if (priv->sections->len == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No query sections for '%s'", xpath); return NULL; } /* success */ return g_steal_pointer(&self); } /** * xb_query_new: * @silo: a #XbSilo * @xpath: The XPath query * @error: the #GError, or %NULL * * Creates a query to be used by @silo. It may be quicker to create a query * manually and re-use it multiple times. * * Returns: (transfer full): a #XbQuery * * Since: 0.1.4 **/ XbQuery * xb_query_new(XbSilo *silo, const gchar *xpath, GError **error) { return xb_query_new_full(silo, xpath, XB_QUERY_FLAG_OPTIMIZE | XB_QUERY_FLAG_USE_INDEXES, error); } static void xb_query_init(XbQuery *self) { } static void xb_query_finalize(GObject *obj) { XbQuery *self = XB_QUERY(obj); XbQueryPrivate *priv = GET_PRIVATE(self); g_ptr_array_unref(priv->sections); g_free(priv->xpath); G_OBJECT_CLASS(xb_query_parent_class)->finalize(obj); } static void xb_query_class_init(XbQueryClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = xb_query_finalize; } libxmlb-0.3.22/src/xb-query.h000066400000000000000000000044411476425255200157620ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include G_BEGIN_DECLS #define XB_TYPE_QUERY (xb_query_get_type()) G_DECLARE_DERIVABLE_TYPE(XbQuery, xb_query, XB, QUERY, GObject) struct _XbQueryClass { GObjectClass parent_class; /*< private >*/ void (*_xb_reserved1)(void); void (*_xb_reserved2)(void); void (*_xb_reserved3)(void); void (*_xb_reserved4)(void); void (*_xb_reserved5)(void); void (*_xb_reserved6)(void); void (*_xb_reserved7)(void); }; /** * XbQueryFlags: * @XB_QUERY_FLAG_NONE: No extra flags to use * @XB_QUERY_FLAG_OPTIMIZE: Optimize the query when possible * @XB_QUERY_FLAG_USE_INDEXES: Use the indexed parameters * @XB_QUERY_FLAG_REVERSE: Reverse the results order * @XB_QUERY_FLAG_FORCE_NODE_CACHE: Always cache the #XbNode objects * * The flags used for queries. **/ typedef enum { XB_QUERY_FLAG_NONE = 0, /* Since: 0.1.6 */ XB_QUERY_FLAG_OPTIMIZE = 1 << 0, /* Since: 0.1.6 */ XB_QUERY_FLAG_USE_INDEXES = 1 << 1, /* Since: 0.1.6 */ XB_QUERY_FLAG_REVERSE = 1 << 2, /* Since: 0.1.15 */ XB_QUERY_FLAG_FORCE_NODE_CACHE = 1 << 3, /* Since: 0.2.0 */ /*< private >*/ XB_QUERY_FLAG_LAST } XbQueryFlags; #include "xb-silo.h" XbQuery * xb_query_new(XbSilo *silo, const gchar *xpath, GError **error) G_GNUC_NON_NULL(1, 2); XbQuery * xb_query_new_full(XbSilo *silo, const gchar *xpath, XbQueryFlags flags, GError **error) G_GNUC_NON_NULL(1, 2); const gchar * xb_query_get_xpath(XbQuery *self) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(xb_query_context_get_limit) guint xb_query_get_limit(XbQuery *self) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(xb_query_context_set_limit) void xb_query_set_limit(XbQuery *self, guint limit) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(xb_query_context_get_flags) XbQueryFlags xb_query_get_flags(XbQuery *self) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(xb_query_context_set_flags) void xb_query_set_flags(XbQuery *self, XbQueryFlags flags) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(xb_value_bindings_bind_str) gboolean xb_query_bind_str(XbQuery *self, guint idx, const gchar *str, GError **error) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(xb_value_bindings_bind_val) gboolean xb_query_bind_val(XbQuery *self, guint idx, guint32 val, GError **error) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-self-test.c000066400000000000000000002736371476425255200165350ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include #include #include "xb-builder-node.h" #include "xb-builder.h" #include "xb-common-private.h" #include "xb-machine.h" #include "xb-node-query.h" #include "xb-opcode-private.h" #include "xb-opcode.h" #include "xb-silo-export.h" #include "xb-silo-private.h" #include "xb-silo-query-private.h" #include "xb-stack-private.h" #include "xb-string-private.h" static GMainLoop *_test_loop = NULL; static guint _test_loop_timeout_id = 0; #define XB_SELF_TEST_INOTIFY_TIMEOUT 10000 /* ms */ static gboolean xb_test_hang_check_cb(gpointer user_data) { g_main_loop_quit(_test_loop); _test_loop_timeout_id = 0; return G_SOURCE_REMOVE; } static void xb_test_loop_run_with_timeout(guint timeout_ms) { g_assert(_test_loop_timeout_id == 0); g_assert(_test_loop == NULL); _test_loop = g_main_loop_new(NULL, FALSE); _test_loop_timeout_id = g_timeout_add(timeout_ms, xb_test_hang_check_cb, NULL); g_main_loop_run(_test_loop); } static void xb_test_loop_quit(void) { if (_test_loop_timeout_id > 0) { g_source_remove(_test_loop_timeout_id); _test_loop_timeout_id = 0; } if (_test_loop != NULL) { g_main_loop_quit(_test_loop); g_main_loop_unref(_test_loop); _test_loop = NULL; } } static gboolean xb_test_import_xml(XbBuilder *self, const gchar *xml, GError **error) { g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_return_val_if_fail(XB_IS_BUILDER(self), FALSE); g_return_val_if_fail(xml != NULL, FALSE); /* add source */ if (!xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, error)) return FALSE; /* success */ xb_builder_import_source(self, source); return TRUE; } static void xb_stack_func(void) { XbOpcode *op1, *op2, *op3, *op4; g_auto(XbOpcode) op1_popped = XB_OPCODE_INIT(); g_auto(XbOpcode) op2_popped = XB_OPCODE_INIT(); g_auto(XbOpcode) op3_popped = XB_OPCODE_INIT(); g_autoptr(XbStack) stack = xb_stack_new(3); /* push three opcodes */ g_assert_true(xb_stack_push(stack, &op3, NULL)); xb_opcode_text_init(op3, "dave"); g_assert_true(xb_stack_push(stack, &op2, NULL)); xb_opcode_integer_init(op2, 1); g_assert_true(xb_stack_push(stack, &op1, NULL)); xb_opcode_func_init(op1, 0); g_assert_false(xb_stack_push(stack, &op4, NULL)); g_assert_null(op4); /* pop the same opcodes */ g_assert_true(xb_stack_pop(stack, &op1_popped, NULL)); g_assert_cmpint(xb_opcode_get_kind(&op1_popped), ==, XB_OPCODE_KIND_FUNCTION); g_assert_true(xb_stack_pop(stack, &op2_popped, NULL)); g_assert_cmpint(xb_opcode_get_kind(&op2_popped), ==, XB_OPCODE_KIND_INTEGER); g_assert_cmpuint(xb_opcode_get_val(&op2_popped), ==, 1); g_assert_true(xb_stack_pop(stack, &op3_popped, NULL)); g_assert_cmpint(xb_opcode_get_kind(&op3_popped), ==, XB_OPCODE_KIND_TEXT); g_assert_cmpstr(xb_opcode_get_str(&op3_popped), ==, "dave"); /* re-add one opcode */ g_assert_true(xb_stack_push(stack, &op4, NULL)); xb_opcode_text_init(op4, "dave again"); g_assert_nonnull(op4); /* finish, cleaning up the stack properly... */ } static void xb_stack_peek_func(void) { XbOpcode *op1, *op2, *op3; g_autoptr(XbStack) stack = xb_stack_new(3); /* push three opcodes */ g_assert_true(xb_stack_push(stack, &op1, NULL)); xb_opcode_func_init(op1, 0); g_assert_true(xb_stack_push(stack, &op2, NULL)); xb_opcode_integer_init(op2, 1); g_assert_true(xb_stack_push(stack, &op3, NULL)); xb_opcode_text_init(op3, "dave"); /* peek the same opcodes */ g_assert_true(xb_stack_peek_head(stack) == op1); g_assert_true(xb_stack_peek_tail(stack) == op3); g_assert_true(xb_stack_peek(stack, 0) == op1); g_assert_true(xb_stack_peek(stack, 1) == op2); g_assert_true(xb_stack_peek(stack, 2) == op3); } static void xb_common_union_func(void) { g_autoptr(GString) xpath = g_string_new(NULL); xb_string_append_union(xpath, "components/component"); g_assert_cmpstr(xpath->str, ==, "components/component"); xb_string_append_union(xpath, "applications/application"); g_assert_cmpstr(xpath->str, ==, "components/component|applications/application"); } static void xb_common_func(void) { g_assert_true(xb_string_search("gimp", "gimp")); g_assert_true(xb_string_search("GIMP", "gimp")); g_assert_true(xb_string_search("The GIMP", "gimp")); g_assert_true(xb_string_search("The GIMP Editor", "gimp")); g_assert_false(xb_string_search("gimp", "")); g_assert_false(xb_string_search("gimp", "imp")); g_assert_false(xb_string_search("the gimp editor", "imp")); g_assert_true(xb_string_token_valid("the")); g_assert_false(xb_string_token_valid(NULL)); g_assert_false(xb_string_token_valid("")); g_assert_false(xb_string_token_valid("a")); g_assert_false(xb_string_token_valid("ab")); } static void xb_common_content_type_func(void) { struct { const gchar *fn; const gchar *ctype; } items[] = {{"test.desktop", "application/x-desktop"}, {"test.quirk", "text/plain"}, {"test.xml", "application/xml"}, {"test.xml.gz.gz.gz", "application/gzip"}, {"test.xml.xz", "application/x-xz"}, {"test.xml.zst", "application/zstd"}, {NULL, NULL}}; for (guint i = 0; items[i].fn != NULL; i++) { gboolean ret; gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autofree gchar *ctype = NULL; g_autofree gchar *fn = g_test_build_filename(G_TEST_DIST, items[i].fn, NULL); g_autoptr(GError) error = NULL; ret = g_file_get_contents(fn, &buf, &bufsz, &error); g_assert_no_error(error); g_assert_true(ret); ctype = xb_content_type_guess(fn, (const guchar *)buf, bufsz); g_assert_cmpstr(ctype, ==, items[i].ctype); } } static void xb_common_searchv_func(void) { const gchar *haystack[] = {"these", "words", "ready", NULL}; const gchar *found[] = {"xxx", "wor", "yyy", NULL}; const gchar *unfound1[] = {"xxx", "yyy", NULL}; const gchar *unfound2[] = {"ords", NULL}; g_assert_true(xb_string_searchv(haystack, found)); g_assert_false(xb_string_searchv(haystack, unfound1)); g_assert_false(xb_string_searchv(haystack, unfound2)); } static void xb_opcodes_kind_func(void) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); g_auto(XbOpcode) op3 = XB_OPCODE_INIT(); xb_opcode_func_init(&op1, 0); xb_opcode_integer_init(&op2, 1); xb_opcode_text_init(&op3, "dave"); /* check kind */ g_assert_cmpint(xb_opcode_get_kind(&op1), ==, XB_OPCODE_KIND_FUNCTION); g_assert_cmpint(xb_opcode_get_kind(&op2), ==, XB_OPCODE_KIND_INTEGER); g_assert_cmpint(xb_opcode_get_kind(&op3), ==, XB_OPCODE_KIND_TEXT); /* to and from string */ g_assert_cmpint(xb_opcode_kind_from_string("TEXT"), ==, XB_OPCODE_KIND_TEXT); g_assert_cmpint(xb_opcode_kind_from_string("FUNC"), ==, XB_OPCODE_KIND_FUNCTION); g_assert_cmpint(xb_opcode_kind_from_string("INTE"), ==, XB_OPCODE_KIND_INTEGER); g_assert_cmpint(xb_opcode_kind_from_string("dave"), ==, XB_OPCODE_KIND_UNKNOWN); g_assert_cmpstr(xb_opcode_kind_to_string(XB_OPCODE_KIND_TEXT), ==, "TEXT"); g_assert_cmpstr(xb_opcode_kind_to_string(XB_OPCODE_KIND_FUNCTION), ==, "FUNC"); g_assert_cmpstr(xb_opcode_kind_to_string(XB_OPCODE_KIND_INTEGER), ==, "INTE"); g_assert_cmpstr(xb_opcode_kind_to_string(XB_OPCODE_KIND_UNKNOWN), ==, NULL); /* integer compare */ g_assert_false(xb_opcode_cmp_val(&op1)); g_assert_true(xb_opcode_cmp_val(&op2)); g_assert_false(xb_opcode_cmp_val(&op3)); /* string compare */ g_assert_false(xb_opcode_cmp_str(&op1)); g_assert_false(xb_opcode_cmp_str(&op2)); g_assert_true(xb_opcode_cmp_str(&op3)); } static void xb_predicate_func(void) { g_autoptr(XbSilo) silo = xb_silo_new(); struct { const gchar *pred; const gchar *str; } tests[] = {{"'a'='b'", "'a','b',eq()"}, {"@a='b'", "'a',attr(),'b',eq()"}, {"@a=='b'", "'a',attr(),'b',eq()"}, {"'a'<'b'", "'a','b',lt()"}, {"999>=123", "999,123,ge()"}, {"not(0)", "0^1,not()"}, {"@a", "'a',attr(),'(null)',ne()"}, {"not(@a)", "'a'^1,attr()^1,not()"}, {"'a'=", "'a',eq()"}, {"='b'", "'b',eq()"}, {"999=\'b\'", "999,'b',eq()"}, {"text()=\'b\'", "text(),'b',eq()"}, {"last()", "last()"}, {"text()~='beef'", "text(),'beef'[beef],search()"}, {"@type~='dead'", "'type',attr(),'dead',search()"}, {"2", "2,position(),eq()"}, {"text()=lower-case('firefox')", "text(),'firefox'^1,lower-case(),eq()"}, {"$'a'=$'b'", "$'a',$'b',eq()"}, {"('a'='b')&&('c'='d')", "'a'^1,'b'^1,eq()^1,'c'^1,'d'^1,eq()^1,and()"}, {"text()==('a','b','c')", "text(),'c'^1,'b'^1,'a'^1,in()"}, /* sentinel */ {NULL, NULL}}; const gchar *invalid[] = {"text(", "text((((((((((((((((((((text()))))))))))))))))))))", NULL}; xb_machine_set_debug_flags(xb_silo_get_machine(silo), XB_MACHINE_DEBUG_FLAG_SHOW_STACK | XB_MACHINE_DEBUG_FLAG_SHOW_PARSING); for (guint i = 0; tests[i].pred != NULL; i++) { g_autofree gchar *str = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbStack) opcodes = NULL; g_debug("testing %s", tests[i].pred); opcodes = xb_machine_parse_full(xb_silo_get_machine(silo), tests[i].pred, -1, XB_MACHINE_PARSE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(opcodes); str = xb_stack_to_string(opcodes); g_assert_nonnull(str); g_assert_cmpstr(str, ==, tests[i].str); } for (guint i = 0; invalid[i] != NULL; i++) { g_autoptr(GError) error = NULL; g_autoptr(XbStack) opcodes = NULL; g_debug("testing %s", invalid[i]); opcodes = xb_machine_parse_full(xb_silo_get_machine(silo), invalid[i], -1, XB_MACHINE_PARSE_FLAG_NONE, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_null(opcodes); } } static void xb_predicate_optimize_func(void) { g_autoptr(XbSilo) silo = xb_silo_new(); struct { const gchar *pred; const gchar *str; } tests[] = {{"@a='b'", "'a',attr(),'b',eq()"}, {"'a'<'b'", "True"}, /* success! */ {"999>=123", "True"}, /* success! */ {"not(0)", "True"}, /* success! */ {"lower-case('Fire')", "'fire'"}, {"upper-case('Τάχιστη')", "'ΤΆΧΙΣΤΗ'"}, {"upper-case(lower-case('Fire'))", "'FIRE'"}, /* 2nd pass */ {"text()==('a','b','c')", "text(),'c'^1,'b'^1,'a'^1,in()"}, /* sentinel */ {NULL, NULL}}; const gchar *invalid[] = {"'a'='b'", "123>=999", "not(1)", NULL}; xb_machine_set_debug_flags(xb_silo_get_machine(silo), XB_MACHINE_DEBUG_FLAG_SHOW_STACK | XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER); for (guint i = 0; tests[i].pred != NULL; i++) { g_autofree gchar *str = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbStack) opcodes = NULL; g_debug("testing %s", tests[i].pred); opcodes = xb_machine_parse_full(xb_silo_get_machine(silo), tests[i].pred, -1, XB_MACHINE_PARSE_FLAG_OPTIMIZE, &error); g_assert_no_error(error); g_assert_nonnull(opcodes); str = xb_stack_to_string(opcodes); g_assert_nonnull(str); g_assert_cmpstr(str, ==, tests[i].str); } for (guint i = 0; invalid[i] != NULL; i++) { g_autoptr(GError) error = NULL; g_autoptr(XbStack) opcodes = NULL; opcodes = xb_machine_parse_full(xb_silo_get_machine(silo), invalid[i], -1, XB_MACHINE_PARSE_FLAG_OPTIMIZE, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_null(opcodes); } } static void xb_builder_func(void) { g_autofree gchar *str = NULL; g_autofree gchar *xml_new = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" "
\n" " dead\n" "
\n" " \n" " gimp.desktop\n" " GIMP & Friendẞ\n" " org.gnome.Gimp.desktop\n" " \n" " \n" " gnome-software.desktop\n" " \n" " \n" " org.hughski.ColorHug2.firmware\n" " \n" " 1.2.3\n" " \n" " \n" "
\n"; /* import from XML */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); g_assert_true(xb_silo_is_valid(silo)); /* convert back to XML */ str = xb_silo_to_string(silo, &error); g_assert_no_error(error); g_assert_nonnull(str); g_debug("\n%s", str); xml_new = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | XB_NODE_EXPORT_FLAG_FORMAT_INDENT, &error); g_assert_no_error(error); g_assert_nonnull(xml_new); g_print("%s", xml_new); g_assert_cmpstr(xml, ==, xml_new); /* check size */ bytes = xb_silo_get_bytes(silo); g_assert_cmpint(g_bytes_get_size(bytes), ==, 628); } static void xb_builder_ensure_invalidate_cb(XbSilo *silo, GParamSpec *pspec, gpointer user_data) { guint *invalidate_cnt = (guint *)user_data; (*invalidate_cnt)++; xb_test_loop_quit(); } static GInputStream * xb_builder_custom_mime_cb(XbBuilderSource *self, XbBuilderSourceCtx *ctx, gpointer user_data, GCancellable *cancellable, GError **error) { gchar *xml = g_strdup_printf("" "" "%s", xb_builder_source_ctx_get_filename(ctx)); return g_memory_input_stream_new_from_data(g_steal_pointer(&xml), -1, g_free); } static void xb_builder_custom_mime_func(void) { gboolean ret; g_autofree gchar *xml = NULL; g_autofree gchar *tmp_desktop = g_build_filename(g_get_tmp_dir(), "temp.desktop", NULL); g_autofree gchar *tmp_xmlb = g_build_filename(g_get_tmp_dir(), "temp.xmlb", NULL); g_autoptr(GError) error = NULL; g_autoptr(GFile) file_desktop = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; /* add support for desktop files */ xb_builder_source_add_adapter(source, "application/x-desktop", xb_builder_custom_mime_cb, NULL, NULL); /* import a source file */ ret = g_file_set_contents(tmp_desktop, "[Desktop Entry]", -1, &error); g_assert_no_error(error); g_assert_true(ret); file_desktop = g_file_new_for_path(tmp_desktop); ret = xb_builder_source_load_file(source, file_desktop, XB_BUILDER_SOURCE_FLAG_WATCH_FILE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); file = g_file_new_for_path(tmp_xmlb); silo = xb_builder_ensure(builder, file, XB_BUILDER_COMPILE_FLAG_WATCH_BLOB, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* check contents */ xml = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(xml); g_print("%s", xml); g_assert_cmpstr("" "temp.desktop" "", ==, xml); } static void xb_builder_source_lzma_func(void) { gboolean ret; g_autofree gchar *path = NULL; g_autofree gchar *tmp_xmlb = g_build_filename(g_get_tmp_dir(), "temp.xmlb", NULL); g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GFile) file_src = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; #ifndef HAVE_LZMA /* not supported */ g_test_skip("compiled without -Dlzma"); return; #endif /* import a source file */ path = g_test_build_filename(G_TEST_DIST, "test.xml.xz", NULL); file_src = g_file_new_for_path(path); if (!g_file_query_exists(file_src, NULL)) { g_test_skip("does not work in subproject test"); return; } ret = xb_builder_source_load_file(source, file_src, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); file = g_file_new_for_path(tmp_xmlb); silo = xb_builder_ensure(builder, file, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); } static void xb_builder_source_zstd_func(void) { gboolean ret; g_autofree gchar *path = NULL; g_autofree gchar *tmp_xmlb = g_build_filename(g_get_tmp_dir(), "temp.xmlb", NULL); g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GFile) file_src = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; /* import a source file */ path = g_test_build_filename(G_TEST_DIST, "test.xml.zst", NULL); file_src = g_file_new_for_path(path); if (!g_file_query_exists(file_src, NULL)) { g_test_skip("does not work in subproject test"); return; } ret = xb_builder_source_load_file(source, file_src, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); file = g_file_new_for_path(tmp_xmlb); silo = xb_builder_ensure(builder, file, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); } static void xb_builder_chained_adapters_func(void) { gboolean ret; g_autofree gchar *xml = NULL; g_autofree gchar *path = NULL; g_autofree gchar *tmp_xmlb = g_build_filename(g_get_tmp_dir(), "temp.xmlb", NULL); g_autoptr(GError) error = NULL; g_autoptr(GFile) file_src = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; /* import a source file */ path = g_test_build_filename(G_TEST_DIST, "test.xml.gz.gz.gz", NULL); file_src = g_file_new_for_path(path); if (!g_file_query_exists(file_src, NULL)) { g_test_skip("does not work in subproject test"); return; } ret = xb_builder_source_load_file(source, file_src, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); file = g_file_new_for_path(tmp_xmlb); silo = xb_builder_ensure(builder, file, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* check contents */ xml = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(xml); g_print("%s", xml); g_assert_cmpstr("Hello world!", ==, xml); } static void xb_builder_ensure_watch_source_func(void) { gboolean ret; guint invalidate_cnt = 0; g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GFile) file_xml = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; g_autofree gchar *tmp_xml = g_build_filename(g_get_tmp_dir(), "temp.xml", NULL); g_autofree gchar *tmp_xmlb = g_build_filename(g_get_tmp_dir(), "temp.xmlb", NULL); #ifdef _WIN32 /* no inotify */ g_test_skip("inotify does not work on mingw"); return; #endif /* import a source file */ ret = g_file_set_contents(tmp_xml, "\n" "gimp", -1, &error); g_assert_no_error(error); g_assert_true(ret); file_xml = g_file_new_for_path(tmp_xml); ret = xb_builder_source_load_file(source, file_xml, XB_BUILDER_SOURCE_FLAG_WATCH_FILE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); file = g_file_new_for_path(tmp_xmlb); g_file_delete(file, NULL, NULL); silo = xb_builder_ensure(builder, file, XB_BUILDER_COMPILE_FLAG_WATCH_BLOB, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); g_assert_true(xb_silo_is_valid(silo)); g_signal_connect(silo, "notify::valid", G_CALLBACK(xb_builder_ensure_invalidate_cb), &invalidate_cnt); /* change source file */ ret = g_file_set_contents(tmp_xml, "\n" "inkscape", -1, &error); g_assert_no_error(error); g_assert_true(ret); xb_test_loop_run_with_timeout(XB_SELF_TEST_INOTIFY_TIMEOUT); g_assert_false(xb_silo_is_valid(silo)); g_assert_cmpint(invalidate_cnt, ==, 1); g_assert_false(xb_silo_is_valid(silo)); } static void xb_builder_ensure_func(void) { gboolean ret; guint invalidate_cnt = 0; g_autofree gchar *bytes1str = NULL; g_autofree gchar *bytes2str = NULL; g_autofree gchar *bytes3str = NULL; g_autofree gchar *tmp_xmlb = g_build_filename(g_get_tmp_dir(), "temp.xmlb", NULL); g_autoptr(GBytes) bytes1 = NULL; g_autoptr(GBytes) bytes2 = NULL; g_autoptr(GBytes) bytes3 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" "
\n" " dead\n" "
\n" " \n" " gimp.desktop\n" " GIMP & Friendẞ\n" " org.gnome.Gimp.desktop\n" " \n" " \n" " gnome-software.desktop\n" " \n" " \n" " org.hughski.ColorHug2.firmware\n" " \n" " 1.2.3\n" " \n" " \n" "
\n"; #ifdef _WIN32 /* no inotify */ g_test_skip("inotify does not work on mingw"); return; #endif /* import some XML */ xb_builder_set_profile_flags(builder, XB_SILO_PROFILE_FLAG_DEBUG); ret = xb_test_import_xml(builder, xml, &error); g_assert_no_error(error); g_assert_true(ret); /* create file if it does not exist */ file = g_file_new_for_path(tmp_xmlb); g_file_delete(file, NULL, NULL); silo = xb_builder_ensure(builder, file, XB_BUILDER_COMPILE_FLAG_WATCH_BLOB, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); g_signal_connect(silo, "notify::valid", G_CALLBACK(xb_builder_ensure_invalidate_cb), &invalidate_cnt); g_assert_cmpint(invalidate_cnt, ==, 0); bytes1 = xb_silo_get_bytes(silo); bytes1str = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, bytes1); /* recreate file if it is invalid */ ret = g_file_replace_contents(file, "dave", 4, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_test_loop_run_with_timeout(XB_SELF_TEST_INOTIFY_TIMEOUT); g_assert_false(xb_silo_is_valid(silo)); g_assert_cmpint(invalidate_cnt, ==, 1); g_clear_object(&silo); silo = xb_builder_ensure(builder, file, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); g_assert_true(xb_silo_is_valid(silo)); bytes2 = xb_silo_get_bytes(silo); bytes2str = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, bytes2); g_assert_cmpstr(bytes1str, !=, bytes2str); g_clear_object(&silo); /* don't recreate file if perfectly valid */ silo = xb_builder_ensure(builder, file, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); g_assert_true(xb_silo_is_valid(silo)); bytes3 = xb_silo_get_bytes(silo); bytes3str = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, bytes3); g_assert_cmpstr(bytes2str, ==, bytes3str); g_clear_object(&silo); g_clear_object(&builder); /* don't re-create for a new builder with the same XML added */ builder = xb_builder_new(); ret = xb_test_import_xml(builder, xml, &error); g_assert_no_error(error); g_assert_true(ret); silo = xb_builder_ensure(builder, file, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); } static gboolean xb_builder_error_cb(XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "engine was busy"); return FALSE; } static void xb_builder_node_vfunc_error_func(void) { gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderFixup) fixup = NULL; g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; /* add fixup */ fixup = xb_builder_fixup_new("AlwaysError", xb_builder_error_cb, NULL, NULL); xb_builder_source_add_fixup(source, fixup); /* import some XML */ ret = xb_builder_source_load_xml(source, "gimp.desktop", XB_BUILDER_SOURCE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_BUSY); g_assert_null(silo); } static gboolean xb_builder_ignore_cb(XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { if (xb_builder_node_get_element(bn) != NULL) xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE); return TRUE; } static void xb_builder_node_vfunc_ignore_func(void) { XbNodeChildIter iter; gboolean ret; const gchar *element; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderFixup) fixup = NULL; g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbBuilderNode) info = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbNode) n = NULL; g_autoptr(XbNode) c = NULL; g_autoptr(XbNode) c2 = NULL; /* add fixup */ fixup = xb_builder_fixup_new("AlwaysIgnore", xb_builder_ignore_cb, NULL, NULL); xb_builder_source_add_fixup(source, fixup); /* import some XML with metadata */ ret = xb_builder_source_load_xml(source, "gimp.desktop", XB_BUILDER_SOURCE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); info = xb_builder_node_insert(NULL, "info", NULL); xb_builder_node_insert_text(info, "foo", "bar", NULL); xb_builder_source_set_info(source, info); xb_builder_import_source(builder, source); /* compile */ silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); n = xb_silo_get_root(silo); g_assert_nonnull(n); element = xb_node_get_element(n); g_assert_null(element); c = xb_node_get_child(n); g_assert_null(c); c = xb_node_get_next(n); g_assert_null(c); xb_node_child_iter_init(&iter, n); ret = xb_node_child_iter_next(&iter, &c2); g_assert_false(ret); g_assert_null(c2); } static gboolean xb_builder_upgrade_appstream_cb(XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { if (g_strcmp0(xb_builder_node_get_element(bn), "application") == 0) { g_autoptr(XbBuilderNode) id = xb_builder_node_get_child(bn, "id", NULL); g_autofree gchar *kind = NULL; if (id != NULL) { kind = g_strdup(xb_builder_node_get_attr(id, "type")); xb_builder_node_remove_attr(id, "type"); } if (kind != NULL) xb_builder_node_set_attr(bn, "type", kind); xb_builder_node_set_element(bn, "component"); } else if (g_strcmp0(xb_builder_node_get_element(bn), "metadata") == 0) { xb_builder_node_set_element(bn, "custom"); } return TRUE; } static void xb_builder_node_vfunc_func(void) { gboolean ret; g_autofree gchar *xml2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderFixup) fixup = NULL; g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; const gchar *xml = " \n" " gimp.desktop\n" " \n"; /* add fixup */ fixup = xb_builder_fixup_new("AppStreamUpgrade", xb_builder_upgrade_appstream_cb, NULL, NULL); xb_builder_source_add_fixup(source, fixup); /* import some XML */ ret = xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* check the XML */ xml2 = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(xml2); g_print("%s\n", xml2); g_assert_cmpstr("" "gimp.desktop" "", ==, xml2); } static gboolean xb_builder_fixup_ignore_node_cb(XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { if (g_strcmp0(xb_builder_node_get_element(bn), "component") == 0) { g_autoptr(XbBuilderNode) id = xb_builder_node_get_child(bn, "id", NULL); if (id != NULL && g_strcmp0(xb_builder_node_get_text(id), "gimp.desktop") == 0) xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE); } else { g_debug("ignoring %s", xb_builder_node_get_element(bn)); } return TRUE; } static void xb_builder_node_vfunc_remove_func(void) { gboolean ret; g_autofree gchar *xml2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderFixup) fixup = NULL; g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; const gchar *xml = " \n" " \n" " gimp.desktop\n" " \n" " \n" " inkscape.desktop\n" " \n" " \n"; /* add fixup */ fixup = xb_builder_fixup_new("RemoveGimp", xb_builder_fixup_ignore_node_cb, NULL, NULL); xb_builder_source_add_fixup(source, fixup); /* import some XML */ ret = xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* check the XML */ xml2 = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(xml2); g_print("%s\n", xml2); g_assert_cmpstr("" "" "inkscape.desktop" "" "", ==, xml2); } static gboolean xb_builder_fixup_root_node_only_cb(XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { g_debug(">%s<", xb_builder_node_get_element(bn)); g_assert_cmpstr(xb_builder_node_get_element(bn), ==, NULL); return TRUE; } static void xb_builder_node_vfunc_depth_func(void) { gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderFixup) fixup = NULL; g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; const gchar *xml = " \n" " \n" " gimp.desktop\n" " \n" " \n"; /* add fixup */ fixup = xb_builder_fixup_new("OnlyRoot", xb_builder_fixup_root_node_only_cb, NULL, NULL); xb_builder_fixup_set_max_depth(fixup, 0); xb_builder_source_add_fixup(source, fixup); /* import some XML */ ret = xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); } static void xb_builder_ignore_invalid_func(void) { gboolean ret; g_autofree gchar *xml2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbSilo) silo = NULL; /* import some correct XML */ ret = xb_test_import_xml(builder, "foobar", &error); g_assert_no_error(error); g_assert_true(ret); /* import some incorrect XML */ ret = xb_test_import_xml(builder, "foobar", &error); g_assert_no_error(error); g_assert_true(ret); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* check the XML */ xml2 = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(xml2); g_print("%s\n", xml2); g_assert_cmpstr("foobar", ==, xml2); } static void xb_builder_empty_func(void) { gboolean ret; g_autofree gchar *str = NULL; g_autofree gchar *xml = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbSilo) silo2 = xb_silo_new(); g_autoptr(XbSilo) silo = NULL; /* import from XML */ silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); g_assert_true(xb_silo_is_valid(silo)); /* check size */ bytes = xb_silo_get_bytes(silo); g_assert_cmpint(g_bytes_get_size(bytes), ==, 40); /* try to dump */ str = xb_silo_to_string(silo, &error); g_assert_no_error(error); g_assert_nonnull(str); g_debug("%s", str); /* try to export */ xml = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(xml); g_clear_error(&error); /* try to query empty silo */ results = xb_silo_query(silo, "components/component", 0, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(results); g_clear_error(&error); /* load blob */ g_assert_nonnull(bytes); ret = xb_silo_load_from_bytes(silo2, bytes, 0, &error); g_assert_no_error(error); g_assert_true(ret); } static void xb_xpath_node_func(void) { g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" " \n" " gimp.desktop\n" " org.gnome.Gimp.desktop\n" " \n" " \n" " org.hughski.ColorHug2.firmware\n" " \n" "\n"; /* import from XML */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get node */ n = xb_silo_query_first(silo, "components/component", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_attr(n, "type"), ==, "desktop"); /* query with text opcodes */ results = xb_node_query(n, "id", 0, &error); g_assert_no_error(error); g_assert_nonnull(results); g_assert_cmpint(results->len, ==, 2); } static void xb_node_data_func(void) { g_autoptr(GError) error = NULL; g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(GBytes) bytes = g_bytes_new("foo", 4); /* import from XML */ silo = xb_silo_new_from_xml("gimp.desktop", &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get node */ n = xb_silo_query_first(silo, "id", &error); g_assert_no_error(error); g_assert_nonnull(n); xb_node_set_data(n, "store", bytes); xb_node_set_data(n, "store", bytes); g_assert_nonnull(xb_node_get_data(n, "store")); g_assert_null(xb_node_get_data(n, "dave")); } static void xb_node_export_func(void) { g_autoptr(GError) error = NULL; g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; g_autofree gchar *xml_default = NULL; g_autofree gchar *xml_collapsed = NULL; /* import from XML */ silo = xb_silo_new_from_xml("", &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get node */ n = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(n); /* export default */ xml_default = xb_node_export(n, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(xml_default); g_assert_cmpstr(xml_default, ==, ""); /* export collapsed */ xml_collapsed = xb_node_export(n, XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY, &error); g_assert_no_error(error); g_assert_nonnull(xml_collapsed); g_assert_cmpstr(xml_collapsed, ==, ""); } static void xb_node_export_collapse_func(void) { const gchar *xml = ""; g_autofree gchar *xml_collapsed = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; /* import from XML */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* export collapsed */ n = xb_silo_get_root(silo); g_assert_nonnull(n); xml_collapsed = xb_node_export(n, XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY, &error); g_assert_no_error(error); g_assert_cmpstr(xml_collapsed, ==, xml); } static void xb_xpath_parent_subnode_func(void) { g_autofree gchar *xml2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) children = NULL; g_autoptr(XbNode) n = NULL; g_autoptr(XbNode) c = NULL; g_autoptr(XbNode) p = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" " \n" " gimp.desktop\n" " org.gnome.Gimp.desktop\n" " \n" " \n" " org.hughski.ColorHug2.firmware\n" " \n" "\n"; /* import from XML */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); xb_silo_set_enable_node_cache(silo, TRUE); /* get node */ n = xb_silo_query_first(silo, "components/component", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_attr(n, "type"), ==, "desktop"); g_assert_cmpint(xb_node_get_depth(n), ==, 1); /* export a child */ xml2 = xb_node_query_export(n, "id", &error); g_assert_cmpstr(xml2, ==, "gimp.desktop"); /* get sibling */ c = xb_node_get_next(n); g_assert_nonnull(c); g_assert_cmpstr(xb_node_get_attr(c, "type"), ==, "firmware"); p = xb_node_get_next(c); g_assert_null(p); g_clear_object(&c); /* use the node to go back up the tree */ c = xb_node_query_first(n, "..", &error); g_assert_no_error(error); g_assert_nonnull(c); g_assert_cmpstr(xb_node_get_attr(c, "origin"), ==, "lvfs"); /* verify this is the parent */ p = xb_node_get_root(n); g_assert_cmpint(xb_node_get_depth(p), ==, 0); g_assert(c == p); children = xb_node_get_children(p); g_assert_nonnull(children); g_assert_cmpint(children->len, ==, 2); } static void xb_xpath_helpers_func(void) { const gchar *tmp; guint64 val; g_autoptr(GError) error = NULL; g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; /* import from XML */ silo = xb_silo_new_from_xml("456", &error); g_assert_no_error(error); g_assert_nonnull(silo); /* as char */ n = xb_silo_get_root(silo); g_assert_nonnull(n); tmp = xb_node_query_text(n, "checksum", &error); g_assert_no_error(error); g_assert_cmpstr(tmp, ==, "456"); tmp = xb_node_query_attr(n, "checksum", "size", &error); g_assert_no_error(error); g_assert_cmpstr(tmp, ==, "123"); /* as uint64 */ val = xb_node_query_text_as_uint(n, "checksum", &error); g_assert_no_error(error); g_assert_cmpint(val, ==, 456); val = xb_node_query_attr_as_uint(n, "checksum", "size", &error); g_assert_no_error(error); g_assert_cmpint(val, ==, 123); } static void xb_xpath_query_func(void) { g_autoptr(GError) error = NULL; g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" " \n" " n/a\n" " \n" "\n"; /* import from XML */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* query with slash */ n = xb_silo_query_first(silo, "components/component/id[text()='n\\/a']", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "n/a"); g_clear_object(&n); /* query with an OR, where the first section contains an unknown element */ n = xb_silo_query_first(silo, "components/dave|components/component/id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "n/a"); g_clear_object(&n); /* query with an OR, where the last section contains an unknown element */ n = xb_silo_query_first(silo, "components/component/id|components/dave", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "n/a"); g_clear_object(&n); /* query using an integer */ n = xb_silo_query_first(silo, "components/component[@timestamp=1631923200]", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_attr(n, "timestamp"), ==, "1631923200"); g_clear_object(&n); /* query with an OR, all sections contains an unknown element */ n = xb_silo_query_first(silo, "components/dave|components/mike", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(n); } static void xb_xpath_incomplete_func(void) { g_autoptr(GError) error = NULL; g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" " \n" " gimp.desktop\n" " \n" "\n"; /* import from XML */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* query with MISSING '[' */ n = xb_silo_query_first(silo, "components/component/id[text()='dave'", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_null(n); } static gboolean xb_builder_fixup_tokenize_cb(XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { if (g_strcmp0(xb_builder_node_get_element(bn), "name") == 0) xb_builder_node_tokenize_text(bn); return TRUE; } static gboolean xb_builder_fixup_strip_inner_cb(XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { if (xb_builder_node_get_first_child(bn) == NULL) xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_STRIP_TEXT); return TRUE; } static void xb_xpath_func(void) { XbNode *n; XbNode *n2; gboolean ret; g_autofree gchar *str = NULL; g_autofree gchar *xml_sub1 = NULL; g_autofree gchar *xml_sub2 = NULL; g_autofree gchar *xml_sub3 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(XbNode) n3 = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderFixup) fixup = NULL; g_autoptr(XbBuilderFixup) fixup2 = NULL; g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" "
\n" " dead\n" "
\n" " \n" " gimp.desktop \n" " org.gnome.Gimp.desktop\n" " Mêẞ\n" " \n" " TRUE\n" " \n" " \n" " \n" " org.hughski.ColorHug2.firmware\n" " \n" "
\n"; /* tokenize specific fields */ fixup = xb_builder_fixup_new("TextTokenize", xb_builder_fixup_tokenize_cb, NULL, NULL); xb_builder_source_add_fixup(source, fixup); /* strip inner nodes without children */ fixup2 = xb_builder_fixup_new("TextStripInner", xb_builder_fixup_strip_inner_cb, NULL, NULL); xb_builder_source_add_fixup(source, fixup2); /* import from XML */ ret = xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* set up debugging */ xb_machine_set_debug_flags(xb_silo_get_machine(silo), XB_MACHINE_DEBUG_FLAG_SHOW_STACK); /* dump to screen */ str = xb_silo_to_string(silo, &error); g_assert_no_error(error); g_assert_nonnull(str); g_debug("\n%s", str); /* query with predicate 'in' */ xb_silo_set_profile_flags(silo, XB_SILO_PROFILE_FLAG_OPTIMIZER); n = xb_silo_query_first( silo, "components/component/id[text()=('gimp.desktop','another.desktop','dave.desktop')]", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with predicate 'in' -- single-entry tuple */ xb_silo_set_profile_flags(silo, XB_SILO_PROFILE_FLAG_OPTIMIZER); n = xb_silo_query_first(silo, "components/component/id[text()=('gimp.desktop')]", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with predicate logical and */ n = xb_silo_query_first( silo, "components/component/custom/value[(@key='KEY') and (text()='TRUE')]/../../id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with predicate logical and; failure */ n = xb_silo_query_first( silo, "components/component/custom/value[(@key='KEY')&&(text()='FALSE')]/../../id", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(n); g_clear_error(&error); g_clear_object(&n); /* query with predicate logical and, alternate form */ n = xb_silo_query_first( silo, "components/component/custom/value[and((@key='KEY'),(text()='TRUE'))]/../../id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query that doesn't find anything */ n = xb_silo_query_first(silo, "dave", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(n); g_clear_error(&error); g_clear_object(&n); n = xb_silo_query_first(silo, "dave/dave", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(n); g_clear_error(&error); g_clear_object(&n); n = xb_silo_query_first(silo, "components/dave", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(n); g_clear_error(&error); g_clear_object(&n); n = xb_silo_query_first(silo, "components/component[@type='dave']/id", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(n); g_clear_error(&error); g_clear_object(&n); n = xb_silo_query_first(silo, "components/component[@percentage>=90]", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(n); g_clear_error(&error); g_clear_object(&n); n = xb_silo_query_first(silo, "components/component/id[text()='dave']", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(n); g_clear_error(&error); g_clear_object(&n); /* query with attr opcodes */ n = xb_silo_query_first(silo, "components/component[@type='firmware']/id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "org.hughski.ColorHug2.firmware"); g_clear_object(&n); /* query with attr opcodes */ n = xb_silo_query_first(silo, "components/component[@type!='firmware']/id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with attr opcodes with quotes */ n = xb_silo_query_first(silo, "components/component[@type='firmware']/id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "org.hughski.ColorHug2.firmware"); g_clear_object(&n); /* query with position */ n = xb_silo_query_first(silo, "components/component[2]/id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "org.hughski.ColorHug2.firmware"); g_clear_object(&n); /* last() with position */ n = xb_silo_query_first(silo, "components/component[last()]/id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "org.hughski.ColorHug2.firmware"); g_clear_object(&n); /* query with attr opcodes that exists */ n = xb_silo_query_first(silo, "components/component[@type]/id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with attrs that do not exist */ n = xb_silo_query_first(silo, "components/component[not(@dave)]/id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with wildcard with predicate */ n = xb_silo_query_first(silo, "components/*[@type]/id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with text opcodes */ n = xb_silo_query_first(silo, "components/header/csum[text()='dead']", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_attr(n, "type"), ==, "sha1"); g_clear_object(&n); /* query with search */ n = xb_silo_query_first(silo, "components/component/id[text()~='gimp']", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query no normalize */ n = xb_silo_query_first(silo, "components/component/name", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "Mêẞ"); g_clear_object(&n); /* query name not UTF-8 */ n = xb_silo_query_first(silo, "components/component/name[text()~='mEss']/../id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with stem */ #ifdef HAVE_LIBSTEMMER xb_machine_set_debug_flags(xb_silo_get_machine(silo), XB_MACHINE_DEBUG_FLAG_SHOW_STACK | XB_MACHINE_DEBUG_FLAG_SHOW_PARSING); n = xb_silo_query_first(silo, "components/component/id[text()~=stem('gimping')]", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); xb_machine_set_debug_flags(xb_silo_get_machine(silo), XB_MACHINE_DEBUG_FLAG_SHOW_STACK); #endif /* query with text:integer */ n = xb_silo_query_first(silo, "components/component/id['123'=123]", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with integer:text */ n = xb_silo_query_first(silo, "components/component/id[123='123']", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with prefix */ n = xb_silo_query_first(silo, "components/component/id[starts-with(text(),'gimp')]", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with suffix */ n = xb_silo_query_first(silo, "components/component/id[ends-with(text(),'.desktop')]", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with contains */ n = xb_silo_query_first(silo, "components/component/id[contains(text(),'imp')]", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with type-conversion */ n = xb_silo_query_first(silo, "components/component[position()=number('2')]/id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "org.hughski.ColorHug2.firmware"); g_clear_object(&n); /* query with another type-conversion */ n = xb_silo_query_first(silo, "components/component['2'=string(2)]/id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query with backtrack */ g_debug("\n%s", xml); n = xb_silo_query_first( silo, "components/component[@type='firmware']/id[text()='org.hughski.ColorHug2.firmware']", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "org.hughski.ColorHug2.firmware"); g_clear_object(&n); /* query with nesting */ g_debug("\n%s", xml); n = xb_silo_query_first( silo, "components/component/id[text()=lower-case(upper-case('Gimp.DESKTOP'))]", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "gimp.desktop"); g_clear_object(&n); /* query for multiple results */ results = xb_silo_query(silo, "components/component/id", 5, &error); g_assert_no_error(error); g_assert_nonnull(results); g_assert_cmpint(results->len, ==, 3); n2 = g_ptr_array_index(results, 2); g_assert_cmpstr(xb_node_get_text(n2), ==, "org.hughski.ColorHug2.firmware"); /* subtree export */ xml_sub1 = xb_node_export(n2, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(xml_sub1); g_assert_cmpstr(xml_sub1, ==, "org.hughski.ColorHug2.firmware"); /* parent of subtree */ n3 = xb_node_get_parent(n2); g_assert(n3 != NULL); xml_sub2 = xb_node_export(n3, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(xml_sub2); g_assert_cmpstr( xml_sub2, ==, "org.hughski.ColorHug2.firmware"); /* only children of parent */ xml_sub3 = xb_node_export(n3, XB_NODE_EXPORT_FLAG_ONLY_CHILDREN, &error); g_assert_no_error(error); g_assert_nonnull(xml_sub3); g_assert_cmpstr(xml_sub3, ==, "org.hughski.ColorHug2.firmware"); } static void xb_manual_token_search_func(void) { XbNode *n; g_autoptr(XbNode) cpt_node = NULL; g_autoptr(XbBuilder) builder = NULL; g_autoptr(XbBuilderNode) bn_root = NULL; g_autoptr(XbBuilderNode) bn = NULL; g_autoptr(XbSilo) silo = NULL; g_autofree gchar *str = NULL; g_autoptr(XbQuery) query = NULL; g_autoptr(GPtrArray) result = NULL; g_autoptr(GError) error = NULL; g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); /* create node tree and add a dummy token manually */ bn_root = xb_builder_node_new("component"); bn = xb_builder_node_new("summary"); xb_builder_node_set_text(bn, "A strategy game", -1); xb_builder_node_add_token(bn, "strategy"); /* or use XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT */ xb_builder_node_add_token(bn, "strategi"); xb_builder_node_add_child(bn_root, bn); builder = xb_builder_new(); xb_builder_import_node(builder, bn_root); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* dump to screen */ str = xb_silo_to_string(silo, &error); g_assert_no_error(error); g_assert_nonnull(str); g_debug("\n%s", str); /* get component node */ cpt_node = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(cpt_node); /* search using the slow, token-less path */ query = xb_query_new(silo, "summary[text()~=?]", &error); g_assert_no_error(error); g_assert_nonnull(query); xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, "strategy", NULL); result = xb_node_query_with_context(cpt_node, query, &context, &error); g_assert_no_error(error); g_assert_nonnull(result); n = XB_NODE(g_ptr_array_index(result, 0)); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "A strategy game"); g_clear_object(&query); g_ptr_array_unref(g_steal_pointer(&result)); /* search for token */ xb_silo_set_profile_flags(silo, ~0); query = xb_query_new(silo, "summary[text()~=?]", &error); g_assert_no_error(error); g_assert_nonnull(query); xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, "strategi", NULL); result = xb_node_query_with_context(cpt_node, query, &context, &error); g_assert_no_error(error); g_assert_nonnull(result); n = XB_NODE(g_ptr_array_index(result, 0)); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "A strategy game"); } static void xb_builder_native_lang_func(void) { gboolean ret; g_autoptr(GError) error = NULL; g_autofree gchar *str = NULL; g_autofree gchar *tmp = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" " \n" "

Wilcommen

\n" "

Hello

\n" "

Salut

\n" "

Goodbye

\n" "

Auf Wiedersehen

\n" "

Au revoir

\n" "
\n" "
\n"; /* import from XML */ ret = xb_test_import_xml(builder, xml, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_add_locale(builder, "fr_FR.UTF-8"); xb_builder_add_locale(builder, "fr_FR"); xb_builder_add_locale(builder, "fr_FR"); xb_builder_add_locale(builder, "fr"); xb_builder_add_locale(builder, "C"); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_SINGLE_LANG, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* test we removed other languages */ str = xb_silo_to_string(silo, &error); g_assert_no_error(error); g_assert_nonnull(str); g_debug("\n%s", str); g_assert_null(g_strstr_len(str, -1, "Wilcommen")); g_assert_null(g_strstr_len(str, -1, "Hello")); g_assert_nonnull(g_strstr_len(str, -1, "Salut")); g_assert_null(g_strstr_len(str, -1, "Goodbye")); g_assert_null(g_strstr_len(str, -1, "Auf Wiedersehen")); g_assert_nonnull(g_strstr_len(str, -1, "Au revoir")); /* test we traversed the tree correctly */ n = xb_silo_query_first(silo, "components/component/*", &error); g_assert_no_error(error); g_assert_nonnull(n); tmp = xb_node_export(n, XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS, &error); g_assert_cmpstr(tmp, ==, "

Salut

Au revoir

"); } static void xb_builder_comments_func(void) { gboolean ret; g_autoptr(GError) error = NULL; g_autofree gchar *str = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" "\n" " \n" " \n" "\n"; /* import XML */ ret = xb_test_import_xml(builder, xml, &error); g_assert_no_error(error); g_assert_true(ret); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* export */ str = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_cmpstr(str, ==, ""); } static void xb_builder_native_lang2_func(void) { gboolean ret; g_autoptr(GError) error = NULL; g_autofree gchar *str = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" " \n" "

Wilcommen

\n" "

Hello

\n" "

Salut

\n" "
\n" "
\n"; /* import from XML */ ret = xb_test_import_xml(builder, xml, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_add_locale(builder, "fr_FR"); xb_builder_add_locale(builder, "fr"); xb_builder_add_locale(builder, "C"); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_SINGLE_LANG, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* test we removed other languages */ str = xb_silo_to_string(silo, &error); g_assert_no_error(error); g_assert_nonnull(str); g_assert_null(g_strstr_len(str, -1, "Wilcommen")); g_assert_null(g_strstr_len(str, -1, "Hello")); g_assert_nonnull(g_strstr_len(str, -1, "Salut")); g_debug("\n%s", str); } static void xb_builder_native_lang_no_locales_func(void) { gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbSilo) silo = NULL; /* import from XML */ ret = xb_test_import_xml(builder, "gimp.desktop", &error); g_assert_no_error(error); g_assert_true(ret); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_SINGLE_LANG, NULL, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_null(silo); } static void xb_xpath_parent_func(void) { XbNode *n; gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" "
\n" " dead\n" "
\n" " \n" " gimp.desktop\n" " org.gnome.Gimp.desktop\n" " \n" " \n" " org.hughski.ColorHug2.firmware\n" " colorhug-client\n" "

Wilcommen!

\n" "

hello!

\n" "

Bonjour!

\n" " GPL-2.0\n" "
\n" "
\n"; /* import from XML */ ret = xb_test_import_xml(builder, xml, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_add_locale(builder, "C"); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get node, no parent */ n = xb_silo_query_first(silo, "components/component[@type='firmware']/id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "org.hughski.ColorHug2.firmware"); g_assert_cmpstr(xb_node_get_element(n), ==, "id"); g_clear_object(&n); /* get node, one parent */ n = xb_silo_query_first(silo, "components/component[@type='firmware']/id/..", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_element(n), ==, "component"); g_clear_object(&n); /* get node, multiple parents */ n = xb_silo_query_first(silo, "components/component[@type='firmware']/id/../..", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_element(n), ==, "components"); g_clear_object(&n); /* descend, ascend, descend */ n = xb_silo_query_first(silo, "components/component[@type='firmware']/pkgname/../project_license", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "GPL-2.0"); g_clear_object(&n); /* descend, ascend, descend */ n = xb_silo_query_first(silo, "components/component/pkgname[text()~='colorhug']/../id", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "org.hughski.ColorHug2.firmware"); g_clear_object(&n); /* get node, too many parents */ n = xb_silo_query_first(silo, "components/component[@type='firmware']/id/../../..", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_null(n); g_clear_error(&error); /* can't go lower than root */ n = xb_silo_query_first(silo, "..", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_null(n); g_clear_error(&error); /* fuzzy substring match */ n = xb_silo_query_first(silo, "components/component/pkgname[text()~='colorhug']", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "colorhug-client"); g_clear_object(&n); /* strlen match */ n = xb_silo_query_first(silo, "components/component/pkgname[string-length(text())==15]", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "colorhug-client"); g_clear_object(&n); /* fuzzy substring match */ n = xb_silo_query_first(silo, "components/component[@type~='firm']/pkgname", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "colorhug-client"); g_clear_object(&n); } static void xb_xpath_prepared_func(void) { XbNode *n; gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbSilo) silo = NULL; g_autoptr(XbQuery) query = NULL; g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); g_autoptr(XbNode) component = NULL; g_autoptr(GPtrArray) components = NULL; const gchar *xml = "\n" " \n" " gimp.desktop\n" " org.gnome.Gimp.desktop\n" " \n" " \n" " org.hughski.ColorHug2.firmware\n" " colorhug-client\n" " \n" "\n"; /* import from XML */ ret = xb_test_import_xml(builder, xml, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_add_locale(builder, "C"); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get first component */ component = xb_silo_query_first(silo, "components/component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* prepared statement on node */ query = xb_query_new(silo, "id[text()=?]/..", &error); g_assert_no_error(error); g_assert_nonnull(query); xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, "gimp.desktop", NULL); components = xb_node_query_with_context(component, query, &context, &error); g_assert_no_error(error); g_assert_nonnull(components); g_assert_cmpint(components->len, ==, 1); n = g_ptr_array_index(components, 0); g_assert_cmpstr(xb_node_get_attr(n, "type"), ==, "desktop"); } static void xb_xpath_query_reverse_func(void) { XbNode *n; gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbSilo) silo = NULL; g_autoptr(XbQuery) query = NULL; g_autoptr(GPtrArray) names = NULL; const gchar *xml = "\n" " foo\n" " bar\n" " baz\n" "\n"; /* import from XML */ ret = xb_test_import_xml(builder, xml, &error); g_assert_no_error(error); g_assert_true(ret); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get first when reversed */ query = xb_query_new_full(silo, "names/name", XB_QUERY_FLAG_REVERSE, &error); g_assert_no_error(error); g_assert_nonnull(query); names = xb_silo_query_with_context(silo, query, NULL, &error); g_assert_no_error(error); g_assert_nonnull(names); g_assert_cmpint(names->len, ==, 3); n = g_ptr_array_index(names, 0); g_assert_cmpstr(xb_node_get_text(n), ==, "baz"); } static void xb_xpath_query_force_node_cache_func(void) { gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbNode) n1 = NULL; g_autoptr(XbNode) n2 = NULL; g_autoptr(XbQuery) query = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" " foo\n" "\n"; /* import from XML */ ret = xb_test_import_xml(builder, xml, &error); g_assert_no_error(error); g_assert_true(ret); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* use a cache for this specific result */ query = xb_query_new_full(silo, "names/name", XB_QUERY_FLAG_FORCE_NODE_CACHE, &error); g_assert_no_error(error); g_assert_nonnull(query); n1 = xb_silo_query_first_with_context(silo, query, NULL, &error); g_assert_no_error(error); g_assert_nonnull(n1); n2 = xb_silo_query_first_with_context(silo, query, NULL, &error); g_assert_no_error(error); g_assert_nonnull(n2); g_assert(n1 == n2); } static void xb_xpath_glob_func(void) { g_autofree gchar *xml2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" " \n" " gimp.desktop\n" " org.gnome.GIMP.desktop\n" " \n" "\n"; /* import from XML */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get node, no parent */ n = xb_silo_query_first(silo, "components/component[@type='desktop']/*", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_element(n), ==, "id"); /* export this one node */ xml2 = xb_node_export(n, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_cmpstr(xml2, ==, "gimp.desktop"); } static void xb_builder_multiple_roots_func(void) { gboolean ret; g_autofree gchar *str = NULL; g_autofree gchar *xml_new = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(XbBuilder) builder = NULL; g_autoptr(XbSilo) silo = NULL; /* import from XML */ builder = xb_builder_new(); ret = xb_test_import_xml(builder, "value", &error); g_assert_no_error(error); g_assert_true(ret); ret = xb_test_import_xml(builder, "value2value3", &error); g_assert_no_error(error); g_assert_true(ret); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* convert back to XML */ str = xb_silo_to_string(silo, &error); g_assert_no_error(error); g_assert_nonnull(str); g_debug("\n%s", str); xml_new = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS, &error); g_assert_no_error(error); g_assert_nonnull(xml_new); g_print("%s", xml_new); g_assert_cmpstr("valuevalue2value3", ==, xml_new); /* query for multiple results */ results = xb_silo_query(silo, "tag", 5, &error); g_assert_no_error(error); g_assert_nonnull(results); g_assert_cmpint(results->len, ==, 3); } static void xb_builder_single_root_func(void) { gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = NULL; g_autoptr(XbSilo) silo = NULL; /* import from XML */ builder = xb_builder_new(); ret = xb_test_import_xml(builder, "value2value3", &error); g_assert_no_error(error); g_assert_true(ret); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_SINGLE_ROOT, NULL, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_null(silo); } static void xb_builder_node_token_max_func(void) { g_autofree gchar *xml = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderNode) components = NULL; g_autoptr(XbBuilderNode) root = xb_builder_node_new(NULL); g_autoptr(XbSilo) silo = NULL; /* create a simple document */ components = xb_builder_node_insert(root, "components", NULL); for (guint i = 0; i < XB_OPCODE_TOKEN_MAX * 2; i++) { g_autofree gchar *tmp = g_strdup_printf("foobarbaz%04u", i); xb_builder_node_add_token(components, tmp); } /* import the doc */ xb_builder_import_node(builder, root); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* check the XML */ xml = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY, &error); g_assert_no_error(error); g_assert_nonnull(xml); g_assert_cmpstr("", ==, xml); } static void xb_builder_node_func(void) { g_autofree gchar *xml = NULL; g_autofree gchar *xml_src = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderNode) child_by_element = NULL; g_autoptr(XbBuilderNode) child_by_text = NULL; g_autoptr(XbBuilderNode) component = NULL; g_autoptr(XbBuilderNode) components = NULL; g_autoptr(XbBuilderNode) id = NULL; g_autoptr(XbBuilderNode) description = xb_builder_node_new("description"); g_autoptr(XbBuilderNode) em = xb_builder_node_new("em"); g_autoptr(XbBuilderNode) empty = NULL; g_autoptr(XbBuilderNode) root = xb_builder_node_new(NULL); g_autoptr(XbSilo) silo = NULL; /* create a simple document */ components = xb_builder_node_insert(root, "components", "origin", "lvfs", NULL); g_assert_cmpint(xb_builder_node_depth(components), ==, 1); component = xb_builder_node_insert(components, "component", NULL); g_assert_cmpint(xb_builder_node_depth(component), ==, 2); xb_builder_node_set_attr(component, "type", "firmware"); xb_builder_node_set_attr(component, "type", "desktop"); g_assert_cmpstr(xb_builder_node_get_attr(component, "type"), ==, "desktop"); g_assert_cmpstr(xb_builder_node_get_attr(component, "dave"), ==, NULL); id = xb_builder_node_new("id"); xb_builder_node_add_flag(id, XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT); xb_builder_node_add_token(id, "foobarbaz"); xb_builder_node_add_child(component, id); xb_builder_node_set_text(id, "gimp.desktop", -1); xb_builder_node_insert_text(component, "icon", "dave", "type", "stock", NULL); g_assert_cmpint(xb_builder_node_depth(id), ==, 3); xb_builder_node_add_flag(em, XB_BUILDER_NODE_FLAG_LITERAL_TEXT); xb_builder_node_set_text(em, "world!", -1); xb_builder_node_set_tail(em, " ", -1); xb_builder_node_add_child(description, em); xb_builder_node_set_text(description, "hello ", -1); xb_builder_node_add_child(component, description); /* no text contents */ empty = xb_builder_node_insert(component, "empty", NULL); xb_builder_node_set_text(empty, NULL, -1); xb_builder_node_set_tail(empty, NULL, -1); /* get specific child */ child_by_element = xb_builder_node_get_child(components, "component", NULL); g_assert_nonnull(child_by_element); g_assert_cmpstr(xb_builder_node_get_element(child_by_element), ==, "component"); child_by_text = xb_builder_node_get_child(component, "id", "gimp.desktop"); g_assert_nonnull(child_by_text); g_assert_cmpstr(xb_builder_node_get_element(child_by_text), ==, "id"); /* check the source XML */ xml_src = xb_builder_node_export(components, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY, &error); g_assert_no_error(error); g_assert_nonnull(xml_src); g_print("%s", xml_src); g_assert_cmpstr("\n" "\n" "gimp.desktop\n" "dave\n" "hello world! \n" "\n" "\n" "\n" "\n", ==, xml_src); /* import the doc */ xb_builder_import_node(builder, root); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* check the XML */ xml = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS | XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY, &error); g_assert_no_error(error); g_assert_nonnull(xml); g_print("%s", xml); g_assert_cmpstr("" "" "gimp.desktop" "dave" "hello world!" "" "" "" "", ==, xml); } static void xb_builder_node_literal_text_func(void) { gboolean ret; g_autofree gchar *xml2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; const gchar *xml = " \n" " \n" "

Really long content\n" "spanning multiple lines\n" "

\n" "
\n" "
\n"; /* import some XML */ ret = xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* check the XML */ xml2 = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(xml2); g_print("%s\n", xml2); g_assert_cmpstr( "" "

Really long content\nspanning multiple lines\n

" "
", ==, xml2); } static void xb_builder_node_source_text_func(void) { gboolean ret; g_autofree gchar *xml2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; const gchar *xml = " \n" " \n" "

Really long content\n" "spanning multiple lines\n" "

\n" "
\n" "
\n"; /* import some XML */ ret = xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* check the XML */ xml2 = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(xml2); g_print("%s\n", xml2); g_assert_cmpstr( "" "

Really long content spanning multiple lines

" "
", ==, xml2); } static void xb_builder_node_info_func(void) { gboolean ret; g_autofree gchar *xml = NULL; g_autofree gchar *tmp_xml = g_build_filename(g_get_tmp_dir(), "temp.xml", NULL); g_autoptr(GError) error = NULL; g_autoptr(XbBuilderSource) import1 = xb_builder_source_new(); g_autoptr(XbBuilderSource) import2 = xb_builder_source_new(); g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbNode) n = NULL; g_autoptr(XbBuilderNode) info1 = NULL; g_autoptr(XbBuilderNode) info2 = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(GFile) file = NULL; /* create a simple document with some info */ ret = g_file_set_contents(tmp_xml, "\n" "dave", -1, &error); g_assert_no_error(error); g_assert_true(ret); info1 = xb_builder_node_insert(NULL, "info", NULL); xb_builder_node_insert_text(info1, "scope", "user", NULL); info2 = xb_builder_node_insert(NULL, "info", NULL); xb_builder_node_insert_text(info2, "scope", "system", NULL); /* import the doc */ file = g_file_new_for_path(tmp_xml); ret = xb_builder_source_load_file(import1, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_source_set_info(import1, info1); xb_builder_source_set_prefix(import1, "local"); xb_builder_import_source(builder, import1); ret = xb_builder_source_load_file(import2, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_source_set_info(import2, info2); xb_builder_source_set_prefix(import2, "local"); xb_builder_import_source(builder, import2); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get info */ n = xb_silo_query_first(silo, "local/component/id[text()='dave']/../info/scope", &error); g_assert_no_error(error); g_assert_nonnull(n); g_assert_cmpstr(xb_node_get_text(n), ==, "user"); /* check the XML */ xml = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS, &error); g_assert_no_error(error); g_assert_nonnull(xml); g_assert_cmpstr("" "" "dave" "" "user" "" "" "" "dave" "" "system" "" "" "", ==, xml); } static void xb_threading_cb(gpointer data, gpointer user_data) { XbSilo *silo = XB_SILO(user_data); gint i = g_random_int_range(0, 50); g_autofree gchar *xpath = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) components = NULL; /* do query */ xpath = g_strdup_printf("components/component/id[text()='%06i.firmware']", i); components = xb_silo_query(silo, xpath, 0, &error); g_assert_no_error(error); g_assert_nonnull(components); g_assert_cmpint(components->len, ==, 1); g_print("."); } static void xb_threading_func(void) { GThreadPool *pool; gboolean ret; guint n_components = 10000; g_autoptr(GError) error = NULL; g_autoptr(GString) xml = g_string_new(NULL); g_autoptr(XbSilo) silo = NULL; #ifdef __s390x__ /* this is run with qemu and takes too much time */ g_test_skip("s390 too slow, skipping"); return; #endif /* create a huge document */ g_string_append(xml, ""); for (guint i = 0; i < n_components; i++) { g_string_append(xml, ""); g_string_append_printf(xml, " %06u.firmware", i); g_string_append(xml, " ColorHug2"); g_string_append(xml, " Firmware"); g_string_append(xml, "

New features!

"); g_string_append(xml, "
"); } g_string_append(xml, "
"); /* import from XML */ silo = xb_silo_new_from_xml(xml->str, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* create thread pool */ pool = g_thread_pool_new(xb_threading_cb, silo, 20, TRUE, &error); g_assert_no_error(error); g_assert_nonnull(pool); /* run threads */ for (guint i = 0; i < 100; i++) { ret = g_thread_pool_push(pool, &i, &error); g_assert_no_error(error); g_assert_true(ret); } g_thread_pool_free(pool, FALSE, TRUE); } typedef struct { guint cnt; GString *str; } XbMarkupHelper; static gboolean xb_markup_head_cb(XbNode *n, gpointer user_data) { XbMarkupHelper *helper = (XbMarkupHelper *)user_data; helper->cnt++; if (xb_node_get_text(n) == NULL) return FALSE; /* start */ if (g_strcmp0(xb_node_get_element(n), "em") == 0) { g_string_append(helper->str, "*"); } else if (g_strcmp0(xb_node_get_element(n), "strong") == 0) { g_string_append(helper->str, "**"); } else if (g_strcmp0(xb_node_get_element(n), "code") == 0) { g_string_append(helper->str, "`"); } /* text */ if (xb_node_get_text(n) != NULL) g_string_append(helper->str, xb_node_get_text(n)); return FALSE; } static gboolean xb_markup_tail_cb(XbNode *n, gpointer user_data) { XbMarkupHelper *helper = (XbMarkupHelper *)user_data; helper->cnt++; /* end */ if (g_strcmp0(xb_node_get_element(n), "em") == 0) { g_string_append(helper->str, "*"); } else if (g_strcmp0(xb_node_get_element(n), "strong") == 0) { g_string_append(helper->str, "**"); } else if (g_strcmp0(xb_node_get_element(n), "code") == 0) { g_string_append(helper->str, "`"); } else if (g_strcmp0(xb_node_get_element(n), "p") == 0) { g_string_append(helper->str, "\n\n"); } /* tail */ if (xb_node_get_tail(n) != NULL) g_string_append(helper->str, xb_node_get_tail(n)); return FALSE; } static void xb_markup_func(void) { gboolean ret; g_autofree gchar *new = NULL; g_autofree gchar *tmp = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; XbMarkupHelper helper = { .cnt = 0, .str = g_string_new(NULL), }; const gchar *xml = "" "

Title:

" "

There is a slight risk of death here!

" "
"; /* import from XML */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* ensure we can round-trip */ tmp = xb_silo_to_string(silo, &error); g_assert_no_error(error); g_assert_nonnull(silo); g_debug("\n%s", tmp); n = xb_silo_get_root(silo); g_assert_nonnull(n); new = xb_node_export(n, XB_NODE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(new); g_assert_cmpstr(xml, ==, new); /* ensure we can convert this to another format */ ret = xb_node_transmogrify(n, xb_markup_head_cb, xb_markup_tail_cb, &helper); g_assert_true(ret); g_assert_cmpstr(helper.str->str, ==, "`Title`:\n\nThere is a *slight* risk of **death** here!\n\n"); g_assert_cmpint(helper.cnt, ==, 14); g_string_free(helper.str, TRUE); } static void xb_speed_func(void) { XbNode *n; gboolean ret; guint n_components = 5000; g_autofree gchar *tmp_xmlb = g_build_filename(g_get_tmp_dir(), "test.xmlb", NULL); g_autofree gchar *xpath1 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(GString) xml = g_string_new(NULL); g_autoptr(GTimer) timer = g_timer_new(); g_autoptr(XbSilo) silo = NULL; #ifdef __s390x__ /* this is run with qemu and takes too much time */ g_test_skip("s390 too slow, skipping"); return; #endif /* create a huge document */ g_string_append(xml, ""); for (guint i = 0; i < n_components; i++) { g_string_append(xml, ""); g_string_append_printf(xml, " %06u.firmware", i); g_string_append(xml, " ColorHug2"); g_string_append(xml, " Firmware"); g_string_append(xml, "

New features!

"); g_string_append(xml, " "); g_string_append(xml, " 2082b5e0"); g_string_append(xml, " "); g_string_append(xml, " "); g_string_append(xml, " fwupd"); g_string_append(xml, " "); g_string_append(xml, " "); g_string_append(xml, " http://com/"); g_string_append(xml, " CC0-1.0"); g_string_append(xml, " GPL-2.0+"); g_string_append(xml, " richard"); g_string_append(xml, " Hughski"); g_string_append(xml, " "); g_string_append( xml, " "); g_string_append( xml, "

stable:

  • Quicker
"); g_string_append(xml, "
"); g_string_append(xml, "
"); g_string_append(xml, "
"); } g_string_append(xml, "
"); /* import from XML */ silo = xb_silo_new_from_xml(xml->str, &error); g_assert_no_error(error); g_assert_nonnull(silo); file = g_file_new_for_path(tmp_xmlb); ret = xb_silo_save_to_file(silo, file, NULL, &error); g_assert_no_error(error); g_assert_true(ret); g_clear_object(&silo); g_print("import+save: %.3fms\n", g_timer_elapsed(timer, NULL) * 1000); g_timer_reset(timer); /* load from file */ silo = xb_silo_new(); ret = xb_silo_load_from_file(silo, file, XB_SILO_LOAD_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); g_print("mmap load: %.3fms\n", g_timer_elapsed(timer, NULL) * 1000); g_timer_reset(timer); /* query best case */ n = xb_silo_query_first(silo, "components/component/id[text()='000000.firmware']", &error); g_assert_no_error(error); g_assert_nonnull(n); g_print("query[first]: %.3fms\n", g_timer_elapsed(timer, NULL) * 1000); g_timer_reset(timer); g_clear_object(&n); /* query worst case */ xpath1 = g_strdup_printf("components/component/id[text()='%06u.firmware']", n_components - 1); n = xb_silo_query_first(silo, xpath1, &error); g_assert_no_error(error); g_assert_nonnull(n); g_print("query[last]: %.3fms\n", g_timer_elapsed(timer, NULL) * 1000); g_timer_reset(timer); g_clear_object(&n); /* query all components */ results = xb_silo_query(silo, "components/component", 0, &error); g_assert_no_error(error); g_assert_nonnull(results); g_assert_cmpint(results->len, ==, n_components); g_print("query[all]: %.3fms\n", g_timer_elapsed(timer, NULL) * 1000); g_timer_reset(timer); /* factorial search */ for (guint i = 0; i < n_components; i += 20) { g_autofree gchar *xpath2 = NULL; xpath2 = g_strdup_printf( "components/component[@type='firmware']/id[text()='%06u.firmware']", i); n = xb_silo_query_first(silo, xpath2, &error); g_assert_no_error(error); g_assert_nonnull(n); g_clear_object(&n); } g_print("query[x%u]: %.3fms\n", n_components, g_timer_elapsed(timer, NULL) * 1000); g_timer_reset(timer); /* factorial search, again */ for (guint i = 0; i < n_components; i += 20) { g_autofree gchar *xpath2 = NULL; xpath2 = g_strdup_printf( "components/component[@type='firmware']/id[text()='%06u.firmware']", i); n = xb_silo_query_first(silo, xpath2, &error); g_assert_no_error(error); g_assert_nonnull(n); g_clear_object(&n); } g_print("query[x%u]: %.3fms\n", n_components, g_timer_elapsed(timer, NULL) * 1000); g_timer_reset(timer); /* create an index */ ret = xb_silo_query_build_index(silo, "components/component/id", NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = xb_silo_query_build_index(silo, "components/component/id[text()='dave']", NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = xb_silo_query_build_index(silo, "components/component/DAVE", NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = xb_silo_query_build_index(silo, "components/component", "type", &error); g_assert_no_error(error); g_assert_true(ret); g_print("create index: %.3fms\n", g_timer_elapsed(timer, NULL) * 1000); g_timer_reset(timer); /* index not found */ n = xb_silo_query_first(silo, "components[text()=$'dave']", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_null(n); g_clear_error(&error); /* do the search again, this time with an index */ g_timer_reset(timer); for (guint i = 0; i < n_components; i += 20) { g_autofree gchar *xpath2 = NULL; xpath2 = g_strdup_printf( "components/component[attr($'type')=$'firmware']/id[text()=$'%06u.firmware']", i); n = xb_silo_query_first(silo, xpath2, &error); g_assert_no_error(error); g_assert_nonnull(n); g_clear_object(&n); } g_print("query[x%u]: %.3fms\n", n_components, g_timer_elapsed(timer, NULL) * 1000); } int main(int argc, char **argv) { g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_setenv("G_MESSAGES_DEBUG", "all", TRUE); setlocale(LC_ALL, ""); /* tests go here */ g_test_add_func("/libxmlb/common", xb_common_func); g_test_add_func("/libxmlb/common{content-type}", xb_common_content_type_func); g_test_add_func("/libxmlb/common{searchv}", xb_common_searchv_func); g_test_add_func("/libxmlb/common{union}", xb_common_union_func); g_test_add_func("/libxmlb/opcodes", xb_predicate_func); g_test_add_func("/libxmlb/opcodes{optimize}", xb_predicate_optimize_func); g_test_add_func("/libxmlb/opcodes{kind}", xb_opcodes_kind_func); g_test_add_func("/libxmlb/stack", xb_stack_func); g_test_add_func("/libxmlb/stack{peek}", xb_stack_peek_func); g_test_add_func("/libxmlb/node{data}", xb_node_data_func); g_test_add_func("/libxmlb/node{export}", xb_node_export_func); g_test_add_func("/libxmlb/node{export-collapse}", xb_node_export_collapse_func); g_test_add_func("/libxmlb/builder", xb_builder_func); g_test_add_func("/libxmlb/builder{comments}", xb_builder_comments_func); g_test_add_func("/libxmlb/builder{native-lang}", xb_builder_native_lang_func); g_test_add_func("/libxmlb/builder{native-lang-nested}", xb_builder_native_lang2_func); g_test_add_func("/libxmlb/builder{native-lang-locale}", xb_builder_native_lang_no_locales_func); g_test_add_func("/libxmlb/builder{empty}", xb_builder_empty_func); g_test_add_func("/libxmlb/builder{ensure}", xb_builder_ensure_func); g_test_add_func("/libxmlb/builder{ensure-watch-source}", xb_builder_ensure_watch_source_func); g_test_add_func("/libxmlb/builder{node-vfunc}", xb_builder_node_vfunc_func); g_test_add_func("/libxmlb/builder{node-vfunc-remove}", xb_builder_node_vfunc_remove_func); g_test_add_func("/libxmlb/builder{node-vfunc-depth}", xb_builder_node_vfunc_depth_func); g_test_add_func("/libxmlb/builder{node-vfunc-error}", xb_builder_node_vfunc_error_func); g_test_add_func("/libxmlb/builder{node-vfunc-ignore}", xb_builder_node_vfunc_ignore_func); g_test_add_func("/libxmlb/builder{ignore-invalid}", xb_builder_ignore_invalid_func); g_test_add_func("/libxmlb/builder{custom-mime}", xb_builder_custom_mime_func); g_test_add_func("/libxmlb/builder{chained-adapters}", xb_builder_chained_adapters_func); g_test_add_func("/libxmlb/builder{source-lzma}", xb_builder_source_lzma_func); g_test_add_func("/libxmlb/builder{source-zstd}", xb_builder_source_zstd_func); g_test_add_func("/libxmlb/builder-node", xb_builder_node_func); g_test_add_func("/libxmlb/builder-node{token-max}", xb_builder_node_token_max_func); g_test_add_func("/libxmlb/builder-node{info}", xb_builder_node_info_func); g_test_add_func("/libxmlb/builder-node{literal-text}", xb_builder_node_literal_text_func); g_test_add_func("/libxmlb/builder-node{source-text}", xb_builder_node_source_text_func); g_test_add_func("/libxmlb/markup", xb_markup_func); g_test_add_func("/libxmlb/token-search", xb_manual_token_search_func); g_test_add_func("/libxmlb/xpath", xb_xpath_func); g_test_add_func("/libxmlb/xpath-query", xb_xpath_query_func); g_test_add_func("/libxmlb/xpath-query{reverse}", xb_xpath_query_reverse_func); g_test_add_func("/libxmlb/xpath-query{force-node-cache}", xb_xpath_query_force_node_cache_func); g_test_add_func("/libxmlb/xpath{helpers}", xb_xpath_helpers_func); g_test_add_func("/libxmlb/xpath{prepared}", xb_xpath_prepared_func); g_test_add_func("/libxmlb/xpath{incomplete}", xb_xpath_incomplete_func); g_test_add_func("/libxmlb/xpath-parent", xb_xpath_parent_func); g_test_add_func("/libxmlb/xpath-glob", xb_xpath_glob_func); g_test_add_func("/libxmlb/xpath-node", xb_xpath_node_func); g_test_add_func("/libxmlb/xpath-parent-subnode", xb_xpath_parent_subnode_func); g_test_add_func("/libxmlb/multiple-roots", xb_builder_multiple_roots_func); g_test_add_func("/libxmlb/single-root", xb_builder_single_root_func); if (g_test_perf()) { g_test_add_func("/libxmlb/threading", xb_threading_func); g_test_add_func("/libxmlb/speed", xb_speed_func); } return g_test_run(); } libxmlb-0.3.22/src/xb-silo-export-private.h000066400000000000000000000005311476425255200205460ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-silo-export.h" #include "xb-silo-private.h" G_BEGIN_DECLS GString * xb_silo_export_with_root(XbSilo *self, XbSiloNode *sroot, XbNodeExportFlags flags, GError **error) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-silo-export.c000066400000000000000000000171431476425255200171000ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbSilo" #include "config.h" #include #include "xb-node-private.h" #include "xb-silo-export-private.h" #include "xb-silo-node.h" #include "xb-string-private.h" typedef struct { GString *xml; XbNodeExportFlags flags; guint32 off; guint level; } XbSiloExportHelper; static gboolean xb_silo_export_node(XbSilo *self, XbSiloExportHelper *helper, XbSiloNode *sn, GError **error) { XbSiloNode *sn2; const gchar *element_name; helper->off = xb_silo_get_offset_for_node(self, sn); /* add start of opening tag */ if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) { for (guint i = 0; i < helper->level; i++) g_string_append(helper->xml, " "); } element_name = xb_silo_from_strtab(self, sn->element_name, error); if (element_name == NULL) return FALSE; g_string_append_printf(helper->xml, "<%s", element_name); /* add any attributes */ for (guint8 i = 0; i < xb_silo_node_get_attr_count(sn); i++) { XbSiloNodeAttr *a = xb_silo_node_get_attr(sn, i); const gchar *name_unsafe; const gchar *value_unsafe; g_autofree gchar *name = NULL; g_autofree gchar *value = NULL; name_unsafe = xb_silo_from_strtab(self, a->attr_name, error); if (name_unsafe == NULL) return FALSE; name = xb_string_xml_escape(name_unsafe); value_unsafe = xb_silo_from_strtab(self, a->attr_value, error); if (value_unsafe == NULL) return FALSE; value = xb_string_xml_escape(value_unsafe); g_string_append_printf(helper->xml, " %s=\"%s\"", name, value); } /* collapse open/close tags together if no text or children */ if (helper->flags & XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY && xb_silo_node_get_text_idx(sn) == XB_SILO_UNSET && xb_silo_get_child_node(self, sn, NULL) == NULL) { g_string_append(helper->xml, " />"); /* offset by opening tag and single byte sentinel */ helper->off += xb_silo_node_get_size(sn); sn2 = xb_silo_get_node(self, helper->off, error); if (sn2 == NULL) return FALSE; helper->off += xb_silo_node_get_size(sn2); } else { /* finish the opening tag and add any text if it exists */ if (xb_silo_node_get_text_idx(sn) != XB_SILO_UNSET) { const gchar *text_unsafe; g_autofree gchar *text = NULL; text_unsafe = xb_silo_from_strtab(self, xb_silo_node_get_text_idx(sn), error); if (text_unsafe == NULL) return FALSE; text = xb_string_xml_escape(text_unsafe); g_string_append(helper->xml, ">"); g_string_append(helper->xml, text); } else { g_string_append(helper->xml, ">"); if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE) g_string_append(helper->xml, "\n"); } helper->off += xb_silo_node_get_size(sn); /* recurse deeper */ while (TRUE) { XbSiloNode *child = xb_silo_get_node(self, helper->off, error); if (child == NULL) return FALSE; if (!xb_silo_node_has_flag(child, XB_SILO_NODE_FLAG_IS_ELEMENT)) break; helper->level++; if (!xb_silo_export_node(self, helper, child, error)) return FALSE; helper->level--; } /* check for the single byte sentinel */ sn2 = xb_silo_get_node(self, helper->off, error); if (sn2 == NULL) return FALSE; if (xb_silo_node_has_flag(sn2, XB_SILO_NODE_FLAG_IS_ELEMENT)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no seninel at %" G_GUINT32_FORMAT, helper->off); return FALSE; } helper->off += xb_silo_node_get_size(sn2); /* add closing tag */ if ((helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) > 0 && xb_silo_node_get_text_idx(sn) == XB_SILO_UNSET) { for (guint i = 0; i < helper->level; i++) g_string_append(helper->xml, " "); } g_string_append_printf(helper->xml, "", element_name); } /* add any optional tail */ if (xb_silo_node_get_tail_idx(sn) != XB_SILO_UNSET) { const gchar *tail_unsafe; g_autofree gchar *tail = NULL; tail_unsafe = xb_silo_from_strtab(self, xb_silo_node_get_tail_idx(sn), error); if (tail_unsafe == NULL) return FALSE; tail = xb_string_xml_escape(tail_unsafe); g_string_append(helper->xml, tail); } if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE) g_string_append(helper->xml, "\n"); return TRUE; } /* private */ GString * xb_silo_export_with_root(XbSilo *self, XbSiloNode *sroot, XbNodeExportFlags flags, GError **error) { XbSiloNode *sn; XbSiloExportHelper helper = { .flags = flags, .level = 0, .off = sizeof(XbSiloHeader), }; g_return_val_if_fail(XB_IS_SILO(self), NULL); /* this implies the other */ if (flags & XB_NODE_EXPORT_FLAG_ONLY_CHILDREN) flags |= XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS; /* optional subtree export */ if (sroot != NULL) { sn = sroot; if (sn != NULL && flags & XB_NODE_EXPORT_FLAG_ONLY_CHILDREN) { g_autoptr(GError) error_local = NULL; sn = xb_silo_get_child_node(self, sn, &error_local); if (sn == NULL) { if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { g_propagate_error(error, g_steal_pointer(&error_local)); return NULL; } } } } else { sn = xb_silo_get_root_node(self, error); if (sn == NULL) return NULL; } /* no root */ if (sn == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no data to export"); return NULL; } /* root node */ helper.xml = g_string_new(NULL); if ((flags & XB_NODE_EXPORT_FLAG_ADD_HEADER) > 0) g_string_append(helper.xml, "\n"); do { g_autoptr(GError) error_local = NULL; if (!xb_silo_export_node(self, &helper, sn, error)) { g_string_free(helper.xml, TRUE); return NULL; } if ((flags & XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS) == 0) break; sn = xb_silo_get_next_node(self, sn, &error_local); if (sn == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) break; g_propagate_error(error, g_steal_pointer(&error_local)); return NULL; } } while (TRUE); /* success */ return helper.xml; } /** * xb_silo_export: * @self: a #XbSilo * @flags: some #XbNodeExportFlags, e.g. #XB_NODE_EXPORT_FLAG_NONE * @error: the #GError, or %NULL * * Exports the silo back to XML. * * Returns: XML data, or %NULL for an error * * Since: 0.1.0 **/ gchar * xb_silo_export(XbSilo *self, XbNodeExportFlags flags, GError **error) { GString *xml; g_return_val_if_fail(XB_IS_SILO(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); xml = xb_silo_export_with_root(self, NULL, flags, error); if (xml == NULL) return NULL; return g_string_free(xml, FALSE); } /** * xb_silo_export_file: * @self: a #XbSilo * @file: a #GFile * @flags: some #XbNodeExportFlags, e.g. #XB_NODE_EXPORT_FLAG_NONE * @cancellable: a #GCancellable, or %NULL * @error: the #GError, or %NULL * * Exports the silo back to an XML file. * * Returns: %TRUE on success * * Since: 0.1.2 **/ gboolean xb_silo_export_file(XbSilo *self, GFile *file, XbNodeExportFlags flags, GCancellable *cancellable, GError **error) { g_autoptr(GString) xml = NULL; g_return_val_if_fail(XB_IS_SILO(self), FALSE); g_return_val_if_fail(G_IS_FILE(file), FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); xml = xb_silo_export_with_root(self, NULL, flags, error); if (xml == NULL) return FALSE; return g_file_replace_contents(file, xml->str, xml->len, NULL, /* etag */ FALSE, /* make-backup */ G_FILE_CREATE_NONE, NULL, /* new etag */ cancellable, error); } libxmlb-0.3.22/src/xb-silo-export.h000066400000000000000000000007541476425255200171050ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "xb-node.h" #include "xb-silo.h" G_BEGIN_DECLS gchar * xb_silo_export(XbSilo *self, XbNodeExportFlags flags, GError **error) G_GNUC_NON_NULL(1); gboolean xb_silo_export_file(XbSilo *self, GFile *file, XbNodeExportFlags flags, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); G_END_DECLS libxmlb-0.3.22/src/xb-silo-node.c000066400000000000000000000022441476425255200165000ustar00rootroot00000000000000/* * Copyright 2025 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "xb-silo-node.h" /* private */ gchar * xb_silo_node_to_string(const XbSiloNode *self) { GString *str = g_string_new("XbSiloNode:\n"); g_string_append_printf(str, " flags: 0x%x\n", self->flags); g_string_append_printf(str, " attr_count: %u\n", self->attr_count); if (self->flags & XB_SILO_NODE_FLAG_IS_ELEMENT) { if (self->element_name != XB_SILO_UNSET) g_string_append_printf(str, " element_name: %u\n", self->element_name); if (self->parent != XB_SILO_UNSET) g_string_append_printf(str, " parent: @%u\n", self->parent); if (self->next != XB_SILO_UNSET) g_string_append_printf(str, " next: @%u\n", self->next); if (self->text != XB_SILO_UNSET) g_string_append_printf(str, " text: %u\n", self->text); if (self->tail != XB_SILO_UNSET) g_string_append_printf(str, " tail: %u\n", self->tail); } for (guint idx = 0; idx < self->attr_count; idx++) { XbSiloNodeAttr *attr = xb_silo_node_get_attr(self, idx); g_string_append_printf(str, " attr: %u=%u\n", attr->attr_name, attr->attr_value); } return g_string_free(str, FALSE); } libxmlb-0.3.22/src/xb-silo-node.h000066400000000000000000000055411476425255200165100ustar00rootroot00000000000000/* * Copyright 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-compile.h" #define XB_SILO_UNSET 0xffffffff typedef enum { XB_SILO_NODE_FLAG_NONE = 0, XB_SILO_NODE_FLAG_IS_ELEMENT = 1 << 0, XB_SILO_NODE_FLAG_IS_TOKENIZED = 1 << 1, } XbSiloNodeFlag; typedef struct __attribute__((packed)) { guint8 flags : 2; guint8 attr_count : 6; guint8 token_count; /* ONLY when is_element */ guint32 element_name; /* ONLY when is_element: from strtab */ guint32 parent; /* ONLY when is_element: from 0 */ guint32 next; /* ONLY when is_element: from 0 */ guint32 text; /* ONLY when is_element: from strtab */ guint32 tail; /* ONLY when is_element: from strtab */ /* guint32 attrs[attr_count]; guint32 tokens[token_count]; */ } XbSiloNode; typedef struct __attribute__((packed)) { guint32 attr_name; /* from strtab */ guint32 attr_value; /* from strtab */ } XbSiloNodeAttr; gchar * xb_silo_node_to_string(const XbSiloNode *self) G_GNUC_NON_NULL(1); /* private */ static inline gboolean xb_silo_node_has_flag(const XbSiloNode *self, XbSiloNodeFlag flag) { return (self->flags & flag) > 0; } static inline guint32 xb_silo_node_get_size(const XbSiloNode *self) { if (xb_silo_node_has_flag(self, XB_SILO_NODE_FLAG_IS_ELEMENT)) { guint8 sz = sizeof(XbSiloNode); sz += self->attr_count * sizeof(XbSiloNodeAttr); sz += self->token_count * sizeof(guint32); return sz; } /* sentinel */ return sizeof(guint8); } /* private */ static inline guint8 xb_silo_node_get_flags(const XbSiloNode *self) { return self->flags; } /* private */ static inline guint32 xb_silo_node_get_text_idx(const XbSiloNode *self) { return self->text; } /* private */ static inline guint32 xb_silo_node_get_tail_idx(const XbSiloNode *self) { return self->tail; } /* private */ static inline guint8 xb_silo_node_get_attr_count(const XbSiloNode *self) { return self->attr_count; } /* private */ static inline XbSiloNodeAttr * xb_silo_node_get_attr(const XbSiloNode *self, guint8 idx) { guint32 off = sizeof(XbSiloNode); off += sizeof(XbSiloNodeAttr) * idx; return (XbSiloNodeAttr *)(((guint8 *)self) + off); } /* private */ static inline guint8 xb_silo_node_get_token_count(const XbSiloNode *self) { return self->token_count; } /* private */ static inline guint32 xb_silo_node_get_token_idx(const XbSiloNode *self, guint idx) { guint32 off = 0; guint32 stridx; /* not valid */ if (!xb_silo_node_has_flag(self, XB_SILO_NODE_FLAG_IS_ELEMENT)) return XB_SILO_UNSET; if (!xb_silo_node_has_flag(self, XB_SILO_NODE_FLAG_IS_TOKENIZED)) return XB_SILO_UNSET; /* calculate offset to token */ off += sizeof(XbSiloNode); off += self->attr_count * sizeof(XbSiloNodeAttr); off += idx * sizeof(guint32); memcpy(&stridx, (guint8 *)self + off, sizeof(stridx)); return stridx; } libxmlb-0.3.22/src/xb-silo-private.h000066400000000000000000000050121476425255200172260ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-machine.h" #include "xb-node.h" #include "xb-query.h" #include "xb-silo-node.h" #include "xb-silo.h" #include "xb-string-private.h" G_BEGIN_DECLS /* 32 bytes, native byte order */ typedef struct __attribute__((packed)) { guint32 magic; guint32 version; XbGuid guid; guint16 strtab_ntags; guint8 padding[2]; guint32 strtab; guint64 filesz; } XbSiloHeader; #define XB_SILO_MAGIC_BYTES 0x624c4d58 #define XB_SILO_VERSION 0x00000009 typedef struct { /*< private >*/ XbSiloNode *sn; guint position; } XbSiloQueryData; const gchar * xb_silo_from_strtab(XbSilo *self, guint32 offset, GError **error) G_GNUC_NON_NULL(1); gboolean xb_silo_strtab_index_insert(XbSilo *self, guint32 offset, GError **error) G_GNUC_NON_NULL(1); guint32 xb_silo_strtab_index_lookup(XbSilo *self, const gchar *str) G_GNUC_NON_NULL(1); XbSiloNode * xb_silo_get_node(XbSilo *self, guint32 off, GError **error) G_GNUC_NON_NULL(1); XbMachine * xb_silo_get_machine(XbSilo *self) G_GNUC_NON_NULL(1); guint32 xb_silo_get_strtab(XbSilo *self) G_GNUC_NON_NULL(1); guint32 xb_silo_get_strtab_idx(XbSilo *self, const gchar *element) G_GNUC_NON_NULL(1); guint32 xb_silo_get_offset_for_node(XbSilo *self, XbSiloNode *n) G_GNUC_NON_NULL(1, 2); XbSiloNode * xb_silo_get_root_node(XbSilo *self, GError **error) G_GNUC_NON_NULL(1); XbSiloNode * xb_silo_get_parent_node(XbSilo *self, XbSiloNode *n, GError **error) G_GNUC_NON_NULL(1, 2); XbSiloNode * xb_silo_get_next_node(XbSilo *self, XbSiloNode *n, GError **error) G_GNUC_NON_NULL(1, 2); XbSiloNode * xb_silo_get_child_node(XbSilo *self, XbSiloNode *n, GError **error) G_GNUC_NON_NULL(1, 2); const gchar * xb_silo_get_node_element(XbSilo *self, XbSiloNode *n, GError **error) G_GNUC_NON_NULL(1, 2); XbSiloNodeAttr * xb_silo_get_node_attr_by_str(XbSilo *self, XbSiloNode *n, const gchar *name) G_GNUC_NON_NULL(1, 2, 3); guint xb_silo_get_node_depth(XbSilo *self, XbSiloNode *n) G_GNUC_NON_NULL(1, 2); XbNode * xb_silo_create_node(XbSilo *self, XbSiloNode *sn, gboolean force_node_cache) G_GNUC_NON_NULL(1); GTimer * xb_silo_start_profile(XbSilo *self) G_GNUC_NON_NULL(1); void xb_silo_add_profile(XbSilo *self, GTimer *timer, const gchar *fmt, ...) G_GNUC_PRINTF(3, 4) G_GNUC_NON_NULL(1); gboolean xb_silo_is_empty(XbSilo *self) G_GNUC_NON_NULL(1); void xb_silo_uninvalidate(XbSilo *self) G_GNUC_NON_NULL(1); XbSiloProfileFlags xb_silo_get_profile_flags(XbSilo *self) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-silo-query-private.h000066400000000000000000000013571476425255200204010ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "xb-query-context.h" #include "xb-query.h" #include "xb-silo-query.h" G_BEGIN_DECLS GPtrArray * xb_silo_query_sn_with_root(XbSilo *self, XbNode *n, const gchar *xpath, guint limit, GError **error) G_GNUC_NON_NULL(1, 3); GPtrArray * xb_silo_query_with_root(XbSilo *self, XbNode *n, const gchar *xpath, guint limit, GError **error) G_GNUC_NON_NULL(1, 3); GPtrArray * xb_silo_query_with_root_full(XbSilo *self, XbNode *n, XbQuery *query, XbQueryContext *context, gboolean first_result_only, GError **error) G_GNUC_NON_NULL(1, 3); G_END_DECLS libxmlb-0.3.22/src/xb-silo-query.c000066400000000000000000000537661476425255200167370ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbSilo" #include "config.h" #include #include #include "xb-node-private.h" #include "xb-opcode-private.h" #include "xb-opcode.h" #include "xb-query-private.h" #include "xb-silo-node.h" #include "xb-silo-query-private.h" #include "xb-stack-private.h" #include "xb-value-bindings-private.h" static gboolean xb_silo_query_node_matches(XbSilo *self, XbMachine *machine, XbSiloNode *sn, XbQuerySection *section, XbSiloQueryData *query_data, XbValueBindings *bindings, guint bindings_offset, guint *bindings_offset_end_out, gboolean *result, GError **error) { /* we have an index into the string table */ if (section->element_idx != sn->element_name && section->kind != XB_SILO_QUERY_KIND_WILDCARD) { *result = FALSE; return TRUE; } /* for section */ query_data->position += 1; /* check predicates */ if (section->predicates != NULL) { for (guint i = 0; i < section->predicates->len; i++) { XbStack *opcodes = g_ptr_array_index(section->predicates, i); g_auto(XbValueBindings) predicate_bindings = XB_VALUE_BINDINGS_INIT(); guint predicate_bindings_idx = 0; XbValueBindings *predicate_bindings_ptr = NULL; if (bindings != NULL) predicate_bindings_ptr = &predicate_bindings; /* set up the bindings for this predicate */ for (guint k = 0; bindings != NULL && k < xb_stack_get_size(opcodes); k++) { XbOpcode *op = xb_stack_peek(opcodes, k); if (xb_opcode_is_binding(op)) { /* ignore errors as they’ll be caught by xb_machine_run() */ xb_value_bindings_copy_binding(bindings, bindings_offset + predicate_bindings_idx, &predicate_bindings, predicate_bindings_idx); predicate_bindings_idx++; } } /* run the predicate; pass NULL for the bindings iff * (bindings == NULL), as that means we’ve been called * with pre-0.3.0-style pre-bound values */ if (!xb_machine_run_with_bindings(machine, opcodes, predicate_bindings_ptr, result, query_data, error)) return FALSE; bindings_offset += predicate_bindings_idx; } } if (bindings_offset_end_out != NULL) *bindings_offset_end_out = bindings_offset; /* success */ return TRUE; } /** * XbSiloQueryHelperFlags: * @XB_SILO_QUERY_HELPER_NONE: No flags set. * @XB_SILO_QUERY_HELPER_USE_SN: Return #XbSiloNodes as results, rather than * wrapping them in #XbNode. This assumes that they’ll be wrapped later. * @XB_SILO_QUERY_HELPER_FORCE_NODE_CACHE: Always cache the #XbNode objects * * Flags for #XbSiloQueryHelper. * * Since: 0.2.0 */ typedef enum { XB_SILO_QUERY_HELPER_NONE = 0, XB_SILO_QUERY_HELPER_USE_SN = 1 << 0, XB_SILO_QUERY_HELPER_FORCE_NODE_CACHE = 1 << 1, } XbSiloQueryHelperFlags; typedef struct { GPtrArray *sections; /* of XbQuerySection */ GPtrArray *results; /* of XbNode or XbSiloNode (see @flags) */ XbValueBindings *bindings; GHashTable *results_hash; /* of sn:1 */ guint limit; XbSiloQueryHelperFlags flags; XbSiloQueryData *query_data; } XbSiloQueryHelper; static gboolean xb_silo_query_section_add_result(XbSilo *self, XbSiloQueryHelper *helper, XbSiloNode *sn) { if (g_hash_table_lookup(helper->results_hash, sn) != NULL) return FALSE; if (helper->flags & XB_SILO_QUERY_HELPER_USE_SN) { g_ptr_array_add(helper->results, sn); } else { gboolean force_node_cache = (helper->flags & XB_SILO_QUERY_HELPER_FORCE_NODE_CACHE) > 0; g_ptr_array_add(helper->results, xb_silo_create_node(self, sn, force_node_cache)); } g_hash_table_add(helper->results_hash, sn); return helper->results->len == helper->limit; } /* * @parent: (allow-none) */ static gboolean xb_silo_query_section_root(XbSilo *self, XbSiloNode *sn, guint i, guint bindings_offset, XbSiloQueryHelper *helper, GError **error) { XbMachine *machine = xb_silo_get_machine(self); XbSiloQueryData *query_data = helper->query_data; XbQuerySection *section = g_ptr_array_index(helper->sections, i); /* handle parent */ if (section->kind == XB_SILO_QUERY_KIND_PARENT) { XbSiloNode *parent; if (sn == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "cannot obtain parent for root"); return FALSE; } parent = xb_silo_get_parent_node(self, sn, error); if (parent == NULL) return FALSE; if (i == helper->sections->len - 1) { xb_silo_query_section_add_result(self, helper, parent); return TRUE; } return xb_silo_query_section_root(self, parent, i + 1, bindings_offset, helper, error); } /* no node means root */ if (sn == NULL) { sn = xb_silo_get_root_node(self, error); if (sn == NULL) return FALSE; } else { g_autoptr(GError) error_local = NULL; sn = xb_silo_get_child_node(self, sn, &error_local); if (sn == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) return TRUE; g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } /* set up level pointer */ query_data->position = 0; /* continue matching children ".." */ do { XbSiloNode *sn_new; gboolean result = TRUE; guint bindings_offset_end = 0; query_data->sn = sn; if (!xb_silo_query_node_matches(self, machine, sn, section, query_data, helper->bindings, bindings_offset, &bindings_offset_end, &result, error)) return FALSE; if (result) { if (i == helper->sections->len - 1) { if (xb_silo_query_section_add_result(self, helper, sn)) break; } else { if (!xb_silo_query_section_root(self, sn, i + 1, bindings_offset_end, helper, error)) return FALSE; if (helper->results->len > 0 && helper->results->len == helper->limit) break; } } if (sn->next == 0x0) break; sn_new = xb_silo_get_node(self, sn->next, error); if (sn_new == NULL) return FALSE; if (sn_new <= sn) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "silo node was invalid: %p -> %p", sn, sn_new); return FALSE; } sn = sn_new; } while (TRUE); return TRUE; } static gboolean xb_silo_query_part(XbSilo *self, XbSiloNode *sroot, GPtrArray *results, GHashTable *results_hash, XbQuery *query, XbQueryContext *context, gboolean first_result_only, XbSiloQueryData *query_data, XbSiloQueryHelperFlags flags, GError **error) { G_GNUC_BEGIN_IGNORE_DEPRECATIONS XbSiloQueryHelper helper = { .results = results, .bindings = (context != NULL) ? xb_query_context_get_bindings(context) : NULL, .limit = first_result_only ? 1 : (context != NULL) ? xb_query_context_get_limit(context) : xb_query_get_limit(query), .flags = flags, .results_hash = results_hash, .query_data = query_data, }; XbQueryFlags query_flags = (context != NULL) ? xb_query_context_get_flags(context) : xb_query_get_flags(query); G_GNUC_END_IGNORE_DEPRECATIONS /* find each section */ helper.sections = xb_query_get_sections(query); if (query_flags & XB_QUERY_FLAG_FORCE_NODE_CACHE) helper.flags |= XB_SILO_QUERY_HELPER_FORCE_NODE_CACHE; return xb_silo_query_section_root(self, sroot, 0, 0, &helper, error); } /* Returns an array with (element-type XbSiloNode) if * %XB_SILO_QUERY_HELPER_USE_SN is set, and (element-type XbNode) otherwise. */ static GPtrArray * silo_query_with_root(XbSilo *self, XbNode *n, const gchar *xpath, guint limit, XbSiloQueryHelperFlags flags, GError **error) { XbSiloNode *sn = NULL; g_auto(GStrv) split = NULL; g_autoptr(GHashTable) results_hash = g_hash_table_new(g_direct_hash, g_direct_equal); g_autoptr(GPtrArray) results = NULL; g_autoptr(GTimer) timer = xb_silo_start_profile(self); XbSiloQueryData query_data = { .sn = NULL, .position = 0, }; g_return_val_if_fail(XB_IS_SILO(self), NULL); g_return_val_if_fail(xpath != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* empty silo */ if (xb_silo_is_empty(self)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "silo has no data"); return NULL; } if (flags & XB_SILO_QUERY_HELPER_USE_SN) results = g_ptr_array_new_with_free_func(NULL); else results = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); /* subtree query */ if (n != NULL) { sn = xb_node_get_sn(n); if (xpath[0] == '/') { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "XPath node query not supported"); return NULL; } } else { /* assume it's just a root query */ if (xpath[0] == '/') xpath++; } /* do 'or' searches */ split = g_strsplit(xpath, "|", -1); for (guint i = 0; split[i] != NULL; i++) { g_autoptr(GError) error_local = NULL; g_autoptr(XbQuery) query = xb_query_new(self, split[i], &error_local); g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); if (query == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT) && (split[i + 1] != NULL || results->len > 0)) { if (xb_silo_get_profile_flags(self) & XB_SILO_PROFILE_FLAG_DEBUG) { g_debug("ignoring for OR statement: %s", error_local->message); } continue; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to process %s: ", xpath); return NULL; } xb_query_context_set_limit(&context, limit); if (!xb_silo_query_part(self, sn, results, results_hash, query, &context, FALSE, &query_data, flags, error)) { return NULL; } } /* profile */ if (xb_silo_get_profile_flags(self) & XB_SILO_PROFILE_FLAG_XPATH) { xb_silo_add_profile(self, timer, "query on %s with `%s` limit=%u -> %u results", n != NULL ? xb_node_get_element(n) : "/", xpath, limit, results->len); } /* nothing found */ if (results->len == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no results for XPath query '%s'", xpath); return NULL; } return g_steal_pointer(&results); } /** * xb_silo_query_with_root: (skip) * @self: a #XbSilo * @n: (allow-none): a #XbNode * @xpath: an XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` * @limit: maximum number of results to return, or 0 for "all" * @error: the #GError, or %NULL * * Searches the silo using an XPath query, returning up to @limit results. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound * * Since: 0.1.0 **/ GPtrArray * xb_silo_query_with_root(XbSilo *self, XbNode *n, const gchar *xpath, guint limit, GError **error) { return silo_query_with_root(self, n, xpath, limit, XB_SILO_QUERY_HELPER_NONE, error); } /** * xb_silo_query_sn_with_root: (skip) * @self: a #XbSilo * @n: (allow-none): a #XbNode * @xpath: an XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` * @limit: maximum number of results to return, or 0 for "all" * @error: the #GError, or %NULL * * A version of xb_silo_query_with_root() which returns results as #XbSiloNodes, * rather than as #XbNodes. This is intended to be used internally to save on * intermediate #XbNode allocations. * * Returns: (transfer container) (element-type XbSiloNode): results, or %NULL if unfound * * Since: 0.2.0 **/ GPtrArray * xb_silo_query_sn_with_root(XbSilo *self, XbNode *n, const gchar *xpath, guint limit, GError **error) { return silo_query_with_root(self, n, xpath, limit, XB_SILO_QUERY_HELPER_USE_SN, error); } static void _g_ptr_array_reverse(GPtrArray *array) { guint last_idx = array->len - 1; for (guint i = 0; i < array->len / 2; i++) { gpointer tmp = array->pdata[i]; array->pdata[i] = array->pdata[last_idx - i]; array->pdata[last_idx - i] = tmp; } } /** * xb_silo_query_with_root_full: (skip) * @self: a #XbSilo * @n: (allow-none): a #XbNode * @query: an #XbQuery * @context: (nullable) (transfer none): context including values bound to opcodes of type * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if * the query doesn’t need any context * @first_result_only: %TRUE if only the first result is going to be used; this * overrides the limit set in @context, and may perform other optimisations * @error: the #GError, or %NULL * * Searches the silo using an XPath query, returning up to @limit results. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound * * Since: 0.3.0 **/ GPtrArray * xb_silo_query_with_root_full(XbSilo *self, XbNode *n, XbQuery *query, XbQueryContext *context, gboolean first_result_only, GError **error) { XbSiloNode *sn = NULL; g_autoptr(GHashTable) results_hash = g_hash_table_new(g_direct_hash, g_direct_equal); g_autoptr(GPtrArray) results = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_autoptr(GTimer) timer = xb_silo_start_profile(self); XbSiloQueryData query_data = { .sn = NULL, .position = 0, }; G_GNUC_BEGIN_IGNORE_DEPRECATIONS XbQueryFlags query_flags = (context != NULL) ? xb_query_context_get_flags(context) : xb_query_get_flags(query); G_GNUC_END_IGNORE_DEPRECATIONS /* convert the XB_OPCODE_KIND_BOUND_TEXT into a XB_OPCODE_KIND_BOUND_INDEXED_TEXT */ if (context != NULL && query_flags & XB_QUERY_FLAG_USE_INDEXES) { XbValueBindings *bindings = xb_query_context_get_bindings(context); if (!xb_value_bindings_indexed_text_lookup(bindings, self, error)) return NULL; } /* empty silo */ if (xb_silo_is_empty(self)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "silo has no data"); return NULL; } /* subtree query */ if (n != NULL) sn = xb_node_get_sn(n); /* only one query allowed */ if (!xb_silo_query_part(self, sn, results, results_hash, query, context, first_result_only, &query_data, XB_SILO_QUERY_HELPER_NONE, error)) return NULL; /* profile */ if (xb_silo_get_profile_flags(self) & XB_SILO_PROFILE_FLAG_XPATH) { g_autofree gchar *tmp = xb_query_to_string(query); g_autofree gchar *bindings_str = NULL; G_GNUC_BEGIN_IGNORE_DEPRECATIONS guint limit = first_result_only ? 1 : (context != NULL) ? xb_query_context_get_limit(context) : xb_query_get_limit(query); G_GNUC_END_IGNORE_DEPRECATIONS if (context != NULL) { XbValueBindings *bindings = xb_query_context_get_bindings(context); bindings_str = xb_value_bindings_to_string(bindings); } xb_silo_add_profile(self, timer, "query on %s with `%s` [%s] limit=%u -> %u results", n != NULL ? xb_node_get_element(n) : "/", tmp, bindings_str != NULL ? bindings_str : "", limit, results->len); } /* nothing found */ if (results->len == 0) { if (error != NULL) { g_autofree gchar *tmp = xb_query_to_string(query); g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no results for XPath query '%s'", tmp); } return NULL; } /* reverse order */ if (query_flags & XB_QUERY_FLAG_REVERSE) _g_ptr_array_reverse(results); return g_steal_pointer(&results); } /** * xb_silo_query_full: * @self: a #XbSilo * @query: an #XbQuery * @error: the #GError, or %NULL * * Searches the silo using an XPath query. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound * * Since: 0.1.13 **/ GPtrArray * xb_silo_query_full(XbSilo *self, XbQuery *query, GError **error) { return xb_silo_query_with_context(self, query, NULL, error); } /** * xb_silo_query_with_context: * @self: a #XbSilo * @query: an #XbQuery * @context: (nullable) (transfer none): context including values bound to opcodes of type * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if * the query doesn’t need any context * @error: the #GError, or %NULL * * Searches the silo using an XPath query. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound * * Since: 0.3.0 **/ GPtrArray * xb_silo_query_with_context(XbSilo *self, XbQuery *query, XbQueryContext *context, GError **error) { g_return_val_if_fail(XB_IS_SILO(self), NULL); g_return_val_if_fail(XB_IS_QUERY(query), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return xb_silo_query_with_root_full(self, NULL, query, context, FALSE, error); } /** * xb_silo_query_first_full: * @self: a #XbSilo * @query: an #XbQuery * @error: the #GError, or %NULL * * Searches the silo using an XPath query, returning up to one result. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a tiny subset of XPath 1.0 is supported. * * Returns: (transfer full): a #XbNode, or %NULL if unfound * * Since: 0.1.13 **/ XbNode * xb_silo_query_first_full(XbSilo *self, XbQuery *query, GError **error) { return xb_silo_query_first_with_context(self, query, NULL, error); } /** * xb_silo_query_first_with_context: * @self: a #XbSilo * @query: an #XbQuery * @context: (nullable) (transfer none): context including values bound to opcodes of type * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if * the query doesn’t need any context * @error: the #GError, or %NULL * * Searches the silo using an XPath query, returning up to one result. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a tiny subset of XPath 1.0 is supported. * * Returns: (transfer full): a #XbNode, or %NULL if unfound * * Since: 0.3.0 **/ XbNode * xb_silo_query_first_with_context(XbSilo *self, XbQuery *query, XbQueryContext *context, GError **error) { g_autoptr(GPtrArray) results = NULL; results = xb_silo_query_with_root_full(self, NULL, query, context, TRUE, error); if (results == NULL) return NULL; return g_object_ref(g_ptr_array_index(results, 0)); } /** * xb_silo_query: * @self: a #XbSilo * @xpath: an XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` * @limit: maximum number of results to return, or 0 for "all" * @error: the #GError, or %NULL * * Searches the silo using an XPath query, returning up to @limit results. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a subset of XPath is supported. * * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound * * Since: 0.1.0 **/ GPtrArray * xb_silo_query(XbSilo *self, const gchar *xpath, guint limit, GError **error) { g_return_val_if_fail(XB_IS_SILO(self), NULL); g_return_val_if_fail(xpath != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return xb_silo_query_with_root(self, NULL, xpath, limit, error); } /** * xb_silo_query_first: * @self: a #XbSilo * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` * @error: the #GError, or %NULL * * Searches the silo using an XPath query, returning up to one result. * * It is safe to call this function from a different thread to the one that * created the #XbSilo. * * Please note: Only a tiny subset of XPath 1.0 is supported. * * Returns: (transfer full): a #XbNode, or %NULL if unfound * * Since: 0.1.0 **/ XbNode * xb_silo_query_first(XbSilo *self, const gchar *xpath, GError **error) { g_autoptr(GPtrArray) results = NULL; results = xb_silo_query_with_root(self, NULL, xpath, 1, error); if (results == NULL) return NULL; return g_object_ref(g_ptr_array_index(results, 0)); } /** * xb_silo_query_build_index: * @self: a #XbSilo * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` * @attr: (nullable): Attribute name, e.g. `type`, or NULL * @error: the #GError, or %NULL * * Adds the `attr()` or `text()` results of a query to the index. * * Returns: %TRUE for success * * Since: 0.1.4 **/ gboolean xb_silo_query_build_index(XbSilo *self, const gchar *xpath, const gchar *attr, GError **error) { g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) array = NULL; g_return_val_if_fail(XB_IS_SILO(self), FALSE); g_return_val_if_fail(xpath != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* do the query */ array = silo_query_with_root(self, NULL, xpath, 0, XB_SILO_QUERY_HELPER_USE_SN, &error_local); if (array == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT) || g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_debug("ignoring index: %s", error_local->message); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* add each attribute name AND value */ for (guint i = 0; i < array->len; i++) { XbSiloNode *sn = g_ptr_array_index(array, i); if (attr != NULL) { guint8 attr_count = xb_silo_node_get_attr_count(sn); for (guint8 j = 0; j < attr_count; j++) { XbSiloNodeAttr *a = xb_silo_node_get_attr(sn, j); if (!xb_silo_strtab_index_insert(self, a->attr_name, error)) return FALSE; if (!xb_silo_strtab_index_insert(self, a->attr_value, error)) return FALSE; } } else { if (!xb_silo_strtab_index_insert(self, xb_silo_node_get_text_idx(sn), error)) return FALSE; } } /* success */ return TRUE; } libxmlb-0.3.22/src/xb-silo-query.h000066400000000000000000000021301476425255200167170ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "xb-node.h" #include "xb-query-context.h" #include "xb-query.h" #include "xb-silo.h" G_BEGIN_DECLS GPtrArray * xb_silo_query(XbSilo *self, const gchar *xpath, guint limit, GError **error) G_GNUC_NON_NULL(1, 2); GPtrArray * xb_silo_query_full(XbSilo *self, XbQuery *query, GError **error) G_GNUC_NON_NULL(1, 2); GPtrArray * xb_silo_query_with_context(XbSilo *self, XbQuery *query, XbQueryContext *context, GError **error) G_GNUC_NON_NULL(1, 2); XbNode * xb_silo_query_first(XbSilo *self, const gchar *xpath, GError **error) G_GNUC_NON_NULL(1, 2); XbNode * xb_silo_query_first_full(XbSilo *self, XbQuery *query, GError **error) G_GNUC_NON_NULL(1, 2); XbNode * xb_silo_query_first_with_context(XbSilo *self, XbQuery *query, XbQueryContext *context, GError **error) G_GNUC_NON_NULL(1); gboolean xb_silo_query_build_index(XbSilo *self, const gchar *xpath, const gchar *attr, GError **error) G_GNUC_NON_NULL(1, 2); G_END_DECLS libxmlb-0.3.22/src/xb-silo.c000066400000000000000000001456211476425255200155640ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ /** * SECTION:xb-silo * @title: XbSilo * @include: xmlb.h * @stability: Stable * @short_description: A read-only store of parsed XML data * * #XbSilo provides read-only access and querying of a previously parsed blob * of XML data. * * All signal emissions from #XbSilo (currently only #GObject::notify emissions) * will happen in the #GMainContext which is the thread default when the #XbSilo * is constructed. * * This #GMainContext must be iterated for file monitoring using * xb_silo_watch_file() to function correctly. */ #define G_LOG_DOMAIN "XbSilo" #include "config.h" #include #include #include #ifdef HAVE_LIBSTEMMER #include #endif #include "xb-builder.h" #include "xb-common-private.h" #include "xb-machine-private.h" #include "xb-node-private.h" #include "xb-opcode-private.h" #include "xb-silo-node.h" #include "xb-stack-private.h" #include "xb-string-private.h" typedef struct { GMappedFile *mmap; gchar *guid; gboolean valid; GBytes *blob; const guint8 *data; /* pointers into ->blob */ guint32 datasz; guint32 strtab; GHashTable *strtab_tags; GHashTable *strindex; gboolean enable_node_cache; GHashTable *nodes; /* (mutex nodes_mutex) */ GMutex nodes_mutex; GHashTable *file_monitors; /* (element-type GFile XbSiloFileMonitorItem) (mutex file_monitors_mutex) */ GMutex file_monitors_mutex; XbMachine *machine; XbSiloProfileFlags profile_flags; GString *profile_str; GRWLock query_cache_mutex; GHashTable *query_cache; GMainContext *context; /* (owned) */ #ifdef HAVE_LIBSTEMMER struct sb_stemmer *stemmer_ctx; /* lazy loaded */ GMutex stemmer_mutex; #endif } XbSiloPrivate; typedef struct { GFileMonitor *file_monitor; gulong file_monitor_id; } XbSiloFileMonitorItem; G_DEFINE_TYPE_WITH_PRIVATE(XbSilo, xb_silo, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_silo_get_instance_private(o)) typedef enum { PROP_GUID = 1, PROP_VALID, PROP_ENABLE_NODE_CACHE, } XbSiloProperty; static GParamSpec *obj_props[PROP_ENABLE_NODE_CACHE + 1] = { NULL, }; /* private */ GTimer * xb_silo_start_profile(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); /* nothing to do; g_timer_new() does a syscall to clock_gettime() which * is best avoided if not needed */ if (!priv->profile_flags) return NULL; return g_timer_new(); } /* private */ void xb_silo_add_profile(XbSilo *self, GTimer *timer, const gchar *fmt, ...) { XbSiloPrivate *priv = GET_PRIVATE(self); va_list args; g_autoptr(GString) str = NULL; /* nothing to do */ if (!priv->profile_flags) return; str = g_string_new(""); /* add duration */ if (timer != NULL) { g_string_append_printf(str, "%.2fms", g_timer_elapsed(timer, NULL) * 1000); for (guint i = str->len; i < 12; i++) g_string_append(str, " "); } /* add varargs */ va_start(args, fmt); g_string_append_vprintf(str, fmt, args); va_end(args); /* do the right thing */ if (priv->profile_flags & XB_SILO_PROFILE_FLAG_DEBUG) g_debug("%s", str->str); if (priv->profile_flags & XB_SILO_PROFILE_FLAG_APPEND) g_string_append_printf(priv->profile_str, "%s\n", str->str); /* reset automatically */ if (timer != NULL) g_timer_reset(timer); } /* private */ static gchar * xb_silo_stem(XbSilo *self, const gchar *value) { #ifdef HAVE_LIBSTEMMER XbSiloPrivate *priv = GET_PRIVATE(self); const gchar *tmp; gsize len_dst; gsize len_src; g_autofree gchar *value_casefold = NULL; g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->stemmer_mutex); g_return_val_if_fail(locker != NULL, NULL); /* not enabled */ value_casefold = g_utf8_casefold(value, -1); if (priv->stemmer_ctx == NULL) priv->stemmer_ctx = sb_stemmer_new("en", NULL); /* stem */ len_src = strlen(value_casefold); tmp = (const gchar *)sb_stemmer_stem(priv->stemmer_ctx, (guchar *)value_casefold, (gint)len_src); len_dst = (gsize)sb_stemmer_length(priv->stemmer_ctx); if (len_src == len_dst) return g_steal_pointer(&value_casefold); return g_strndup(tmp, len_dst); #else return g_utf8_casefold(value, -1); #endif } /* private */ const gchar * xb_silo_from_strtab(XbSilo *self, guint32 offset, GError **error) { XbSiloPrivate *priv = GET_PRIVATE(self); if (G_UNLIKELY(offset == XB_SILO_UNSET)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "offset was unset"); return NULL; } if (offset >= priv->datasz - priv->strtab) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "strtab+offset is outside the data range for %u", offset); return NULL; } return (const gchar *)(priv->data + priv->strtab + offset); } /* private */ gboolean xb_silo_strtab_index_insert(XbSilo *self, guint32 offset, GError **error) { XbSiloPrivate *priv = GET_PRIVATE(self); const gchar *tmp; /* get the string version */ tmp = xb_silo_from_strtab(self, offset, error); if (tmp == NULL) return FALSE; if (g_hash_table_lookup(priv->strindex, tmp) != NULL) return TRUE; g_hash_table_insert(priv->strindex, (gpointer)tmp, GUINT_TO_POINTER(offset)); return TRUE; } /* private */ guint32 xb_silo_strtab_index_lookup(XbSilo *self, const gchar *str) { XbSiloPrivate *priv = GET_PRIVATE(self); gpointer val = NULL; if (!g_hash_table_lookup_extended(priv->strindex, str, NULL, &val)) return XB_SILO_UNSET; return GPOINTER_TO_INT(val); } /* private */ XbSiloNode * xb_silo_get_node(XbSilo *self, guint32 off, GError **error) { XbSiloPrivate *priv = GET_PRIVATE(self); if (G_UNLIKELY(off >= priv->strtab)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "offset %u is outside the expected range", off); return NULL; } return (XbSiloNode *)(priv->data + off); } /* private */ guint32 xb_silo_get_offset_for_node(XbSilo *self, XbSiloNode *n) { XbSiloPrivate *priv = GET_PRIVATE(self); return ((const guint8 *)n) - priv->data; } /* private */ guint32 xb_silo_get_strtab(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); return priv->strtab; } /* private */ XbSiloNode * xb_silo_get_root_node(XbSilo *self, GError **error) { XbSiloPrivate *priv = GET_PRIVATE(self); if (G_UNLIKELY(priv->blob == NULL)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no blob loaded"); return NULL; } if (G_UNLIKELY(g_bytes_get_size(priv->blob) < sizeof(XbSiloHeader))) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "blob too small: 0x%x", (guint)g_bytes_get_size(priv->blob)); return NULL; } if (G_UNLIKELY(g_bytes_get_size(priv->blob) == sizeof(XbSiloHeader))) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no node data"); return NULL; } return xb_silo_get_node(self, sizeof(XbSiloHeader), error); } /* private */ XbSiloNode * xb_silo_get_parent_node(XbSilo *self, XbSiloNode *n, GError **error) { if (G_UNLIKELY(n->parent == 0x0)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no parent set for %s", xb_silo_get_node_element(self, n, NULL)); return NULL; } return xb_silo_get_node(self, n->parent, error); } /* private */ XbSiloNode * xb_silo_get_next_node(XbSilo *self, XbSiloNode *n, GError **error) { if (n->next == 0x0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no next node"); return NULL; } return xb_silo_get_node(self, n->next, error); } /* private */ XbSiloNode * xb_silo_get_child_node(XbSilo *self, XbSiloNode *n, GError **error) { XbSiloNode *c; guint32 off = xb_silo_get_offset_for_node(self, n); off += xb_silo_node_get_size(n); /* check for sentinel */ c = xb_silo_get_node(self, off, error); if (c == NULL) return NULL; if (!xb_silo_node_has_flag(c, XB_SILO_NODE_FLAG_IS_ELEMENT)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no child element"); return NULL; } return c; } /** * xb_silo_get_root: * @self: a #XbSilo * * Gets the root node for the silo. (MIGHT BE MORE). * * Returns: (transfer full): A #XbNode, or %NULL for an error * * Since: 0.1.0 **/ XbNode * xb_silo_get_root(XbSilo *self) { XbSiloNode *sn; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(XB_IS_SILO(self), NULL); sn = xb_silo_get_root_node(self, &error_local); if (sn == NULL) { /* if there are no XbSiloNodes, still build a root XbNode */ if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return NULL; g_debug("ignoring: %s", error_local->message); } return xb_silo_create_node(self, sn, FALSE); } /* private */ guint32 xb_silo_get_strtab_idx(XbSilo *self, const gchar *element) { XbSiloPrivate *priv = GET_PRIVATE(self); gpointer value = NULL; if (!g_hash_table_lookup_extended(priv->strtab_tags, element, NULL, &value)) return XB_SILO_UNSET; return GPOINTER_TO_UINT(value); } /** * xb_silo_to_string: * @self: a #XbSilo * @error: the #GError, or %NULL * * Converts the silo to an internal string representation. This is only * really useful for debugging #XbSilo itself. * * Returns: A string, or %NULL for an error * * Since: 0.1.0 **/ gchar * xb_silo_to_string(XbSilo *self, GError **error) { guint32 off = sizeof(XbSiloHeader); XbSiloPrivate *priv = GET_PRIVATE(self); XbSiloHeader *hdr = (XbSiloHeader *)priv->data; g_autoptr(GString) str = g_string_new(NULL); g_return_val_if_fail(XB_IS_SILO(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* sanity check */ if (hdr->strtab > priv->datasz) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "strtab invalid"); return NULL; } g_string_append_printf(str, "magic: %08x\n", (guint)hdr->magic); g_string_append_printf(str, "guid: %s\n", priv->guid); g_string_append_printf(str, "filesz: @%" G_GUINT64_FORMAT "\n", hdr->filesz); g_string_append_printf(str, "strtab: @%" G_GUINT32_FORMAT "\n", hdr->strtab); g_string_append_printf(str, "strtab_ntags: %" G_GUINT16_FORMAT "\n", hdr->strtab_ntags); while (off < priv->strtab) { XbSiloNode *n = xb_silo_get_node(self, off, error); if (n == NULL) return NULL; if (xb_silo_node_has_flag(n, XB_SILO_NODE_FLAG_IS_ELEMENT)) { guint32 idx; const gchar *element_name; g_string_append_printf(str, "NODE @%" G_GUINT32_FORMAT "\n", off); g_string_append_printf(str, "size: %" G_GUINT32_FORMAT "\n", xb_silo_node_get_size(n)); g_string_append_printf(str, "flags: %x\n", xb_silo_node_get_flags(n)); element_name = xb_silo_from_strtab(self, n->element_name, error); if (element_name == NULL) return NULL; g_string_append_printf(str, "element_name: %s [%03u]\n", element_name, n->element_name); g_string_append_printf(str, "next: %" G_GUINT32_FORMAT "\n", n->next); g_string_append_printf(str, "parent: %" G_GUINT32_FORMAT "\n", n->parent); idx = xb_silo_node_get_text_idx(n); if (idx != XB_SILO_UNSET) { const gchar *text = xb_silo_from_strtab(self, idx, error); if (text == NULL) return NULL; g_string_append_printf(str, "text: %s [%03u]\n", text, idx); } idx = xb_silo_node_get_tail_idx(n); if (idx != XB_SILO_UNSET) { const gchar *tail = xb_silo_from_strtab(self, idx, error); if (tail == NULL) return NULL; g_string_append_printf(str, "tail: %s [%03u]\n", tail, idx); } for (guint8 i = 0; i < xb_silo_node_get_attr_count(n); i++) { XbSiloNodeAttr *a = xb_silo_node_get_attr(n, i); const gchar *attr_name; const gchar *attr_value; attr_name = xb_silo_from_strtab(self, a->attr_name, error); if (attr_name == NULL) return NULL; g_string_append_printf(str, "attr_name: %s [%03u]\n", attr_name, a->attr_name); attr_value = xb_silo_from_strtab(self, a->attr_value, error); if (attr_value == NULL) return NULL; g_string_append_printf(str, "attr_value: %s [%03u]\n", attr_value, a->attr_value); } for (guint8 i = 0; i < xb_silo_node_get_token_count(n); i++) { guint32 idx_tmp = xb_silo_node_get_token_idx(n, i); const gchar *token = xb_silo_from_strtab(self, idx_tmp, error); if (token == NULL) return NULL; g_string_append_printf(str, "token: %s [%03u]\n", token, idx_tmp); } } else { g_string_append_printf(str, "SENT @%" G_GUINT32_FORMAT "\n", off); } off += xb_silo_node_get_size(n); } /* add strtab */ g_string_append_printf(str, "STRTAB @%" G_GUINT32_FORMAT "\n", hdr->strtab); for (off = 0; off < priv->datasz - hdr->strtab;) { const gchar *tmp = xb_silo_from_strtab(self, off, NULL); if (tmp == NULL) break; g_string_append_printf(str, "[%03u]: %s\n", off, tmp); off += strlen(tmp) + 1; } /* success */ return g_string_free(g_steal_pointer(&str), FALSE); } /* private */ const gchar * xb_silo_get_node_element(XbSilo *self, XbSiloNode *n, GError **error) { return xb_silo_from_strtab(self, n->element_name, error); } /* private */ XbSiloNodeAttr * xb_silo_get_node_attr_by_str(XbSilo *self, XbSiloNode *n, const gchar *name) { guint8 attr_count; /* calculate offset to first attribute */ attr_count = xb_silo_node_get_attr_count(n); for (guint8 i = 0; i < attr_count; i++) { XbSiloNodeAttr *a = xb_silo_node_get_attr(n, i); const gchar *name_tmp = xb_silo_from_strtab(self, a->attr_name, NULL); if (name_tmp == NULL) return NULL; if (g_strcmp0(name_tmp, name) == 0) return a; } /* nothing matched */ return NULL; } static XbSiloNodeAttr * xb_silo_node_get_attr_by_val(XbSilo *self, XbSiloNode *n, guint32 name) { guint8 attr_count; /* calculate offset to first attribute */ attr_count = xb_silo_node_get_attr_count(n); for (guint8 i = 0; i < attr_count; i++) { XbSiloNodeAttr *a = xb_silo_node_get_attr(n, i); if (a->attr_name == name) return a; } /* nothing matched */ return NULL; } /** * xb_silo_get_size: * @self: a #XbSilo * * Gets the number of nodes in the silo. * * Returns: a integer, or 0 is an empty blob * * Since: 0.1.0 **/ guint xb_silo_get_size(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); guint32 off = sizeof(XbSiloHeader); guint nodes_cnt = 0; g_return_val_if_fail(XB_IS_SILO(self), 0); while (off < priv->strtab) { XbSiloNode *n = xb_silo_get_node(self, off, NULL); if (n == NULL) return 0; if (xb_silo_node_has_flag(n, XB_SILO_NODE_FLAG_IS_ELEMENT)) nodes_cnt += 1; off += xb_silo_node_get_size(n); } /* success */ return nodes_cnt; } /** * xb_silo_is_valid: * @self: a #XbSilo * * Checks is the silo is valid. The usual reason the silo is invalidated is * when the backing mmapped file has changed, or one of the imported files have * been modified. * * Returns: %TRUE if valid * * Since: 0.1.0 **/ gboolean xb_silo_is_valid(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_SILO(self), FALSE); return priv->valid; } /* private */ gboolean xb_silo_is_empty(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_SILO(self), FALSE); return priv->strtab == sizeof(XbSiloHeader); } typedef struct { XbSilo *silo; /* (owned) */ GParamSpec *pspec; /* (owned) */ } SiloNotifyData; static void silo_notify_data_free(SiloNotifyData *data) { g_clear_object(&data->silo); g_clear_pointer(&data->pspec, g_param_spec_unref); g_free(data); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(SiloNotifyData, silo_notify_data_free) static gboolean silo_notify_cb(gpointer user_data) { g_autoptr(SiloNotifyData) data = g_steal_pointer(&user_data); g_object_notify_by_pspec(G_OBJECT(data->silo), data->pspec); return G_SOURCE_REMOVE; } /* Like g_object_notify(), but ensure that the signal is emitted in XbSilo.context. */ static void silo_notify(XbSilo *self, GParamSpec *pspec) { XbSiloPrivate *priv = GET_PRIVATE(self); g_autoptr(SiloNotifyData) data = NULL; data = g_new0(SiloNotifyData, 1); data->silo = g_object_ref(self); data->pspec = g_param_spec_ref(pspec); g_main_context_invoke(priv->context, silo_notify_cb, g_steal_pointer(&data)); } /** * xb_silo_invalidate: * @self: a #XbSilo * * Invalidates a silo. Future calls xb_silo_is_valid() will return %FALSE. * * Since: 0.1.1 **/ void xb_silo_invalidate(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); if (!priv->valid) return; priv->valid = FALSE; silo_notify(self, obj_props[PROP_VALID]); } /* private */ void xb_silo_uninvalidate(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); if (priv->valid) return; priv->valid = TRUE; silo_notify(self, obj_props[PROP_VALID]); } /* private */ guint xb_silo_get_node_depth(XbSilo *self, XbSiloNode *n) { guint depth = 0; while (n->parent != 0) { depth++; n = xb_silo_get_node(self, n->parent, NULL); if (n == NULL) break; } return depth; } /** * xb_silo_get_bytes: * @self: a #XbSilo * * Gets the backing object that created the blob. * * You should never *ever* modify this data. * * Returns: (transfer full): A #GBytes, or %NULL if never set * * Since: 0.1.0 **/ GBytes * xb_silo_get_bytes(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_SILO(self), NULL); if (priv->blob == NULL) return NULL; return g_bytes_ref(priv->blob); } /** * xb_silo_get_guid: * @self: a #XbSilo * * Gets the GUID used to identify this silo. * * Returns: a string, otherwise %NULL * * Since: 0.1.0 **/ const gchar * xb_silo_get_guid(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_SILO(self), NULL); return priv->guid; } /* private */ XbMachine * xb_silo_get_machine(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_SILO(self), NULL); return priv->machine; } /** * xb_silo_load_from_bytes: * @self: a #XbSilo * @blob: a #GBytes * @flags: #XbSiloLoadFlags, e.g. %XB_SILO_LOAD_FLAG_NONE * @error: the #GError, or %NULL * * Loads a silo from memory location. * * Returns: %TRUE for success, otherwise @error is set. * * Since: 0.1.0 **/ gboolean xb_silo_load_from_bytes(XbSilo *self, GBytes *blob, XbSiloLoadFlags flags, GError **error) { XbGuid guid_tmp; XbSiloHeader *hdr; XbSiloPrivate *priv = GET_PRIVATE(self); gsize sz = 0; guint32 off = 0; g_autoptr(GMutexLocker) locker = NULL; g_autoptr(GTimer) timer = xb_silo_start_profile(self); g_return_val_if_fail(XB_IS_SILO(self), FALSE); g_return_val_if_fail(blob != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* no longer valid */ if (priv->enable_node_cache) { locker = g_mutex_locker_new(&priv->nodes_mutex); if (priv->nodes != NULL) g_hash_table_remove_all(priv->nodes); } g_hash_table_remove_all(priv->strtab_tags); g_clear_pointer(&priv->guid, g_free); /* refcount internally */ if (priv->blob != NULL) g_bytes_unref(priv->blob); priv->blob = g_bytes_ref(blob); /* update pointers into blob */ priv->data = g_bytes_get_data(priv->blob, &sz); priv->datasz = (guint32)sz; /* check size */ if (sz < sizeof(XbSiloHeader)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "blob too small"); return FALSE; } /* check header magic */ hdr = (XbSiloHeader *)priv->data; if ((flags & XB_SILO_LOAD_FLAG_NO_MAGIC) == 0) { if (hdr->magic != XB_SILO_MAGIC_BYTES) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "magic incorrect"); return FALSE; } if (hdr->version != XB_SILO_VERSION) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "version incorrect, got %u, expected %d", hdr->version, XB_SILO_VERSION); return FALSE; } } /* check size */ if (hdr->filesz != sz) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "filesz incorrect"); return FALSE; } /* get GUID */ memcpy(&guid_tmp, &hdr->guid, sizeof(guid_tmp)); priv->guid = xb_guid_to_string(&guid_tmp); /* check strtab */ priv->strtab = hdr->strtab; if (priv->strtab > priv->datasz) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "strtab incorrect"); return FALSE; } if (hdr->strtab_ntags > 0 && priv->data[sz - 1] != '\0') { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "strtab invalid, trailing NUL not found"); return FALSE; } /* load strtab_tags */ for (guint16 i = 0; i < hdr->strtab_ntags; i++) { const gchar *tmp = xb_silo_from_strtab(self, off, error); if (tmp == NULL) { g_prefix_error(error, "strtab_ntags incorrect: "); return FALSE; } g_hash_table_insert(priv->strtab_tags, (gpointer)tmp, GUINT_TO_POINTER(off)); off += strlen(tmp) + 1; } /* profile */ xb_silo_add_profile(self, timer, "parse blob"); /* success */ priv->valid = TRUE; return TRUE; } /** * xb_silo_get_profile_string: * @self: a #XbSilo * * Returns the profiling data. This will only return profiling text if * xb_silo_set_profile_flags() was used with %XB_SILO_PROFILE_FLAG_APPEND. * * Returns: text profiling data * * Since: 0.1.1 **/ const gchar * xb_silo_get_profile_string(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_SILO(self), NULL); return priv->profile_str->str; } /** * xb_silo_set_profile_flags: * @self: a #XbSilo * @profile_flags: some #XbSiloProfileFlags, e.g. %XB_SILO_PROFILE_FLAG_DEBUG * * Enables or disables the collection of profiling data. * * Since: 0.1.1 **/ void xb_silo_set_profile_flags(XbSilo *self, XbSiloProfileFlags profile_flags) { XbSiloPrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_SILO(self)); priv->profile_flags = profile_flags; /* proxy */ if (profile_flags & XB_SILO_PROFILE_FLAG_OPTIMIZER) { xb_machine_set_debug_flags(priv->machine, XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER | XB_MACHINE_DEBUG_FLAG_SHOW_SLOW_PATH); } } /** * xb_silo_get_enable_node_cache: * @self: an #XbSilo * * Get #XbSilo:enable-node-cache. * * Since: 0.2.0 */ gboolean xb_silo_get_enable_node_cache(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_SILO(self), FALSE); return priv->enable_node_cache; } /** * xb_silo_set_enable_node_cache: * @self: an #XbSilo * @enable_node_cache: %TRUE to enable the node cache, %FALSE otherwise * * Set #XbSilo:enable-node-cache. * * This is not thread-safe, and can only be called before the #XbSilo is passed * between threads. * * Since: 0.2.0 */ void xb_silo_set_enable_node_cache(XbSilo *self, gboolean enable_node_cache) { XbSiloPrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_SILO(self)); if (priv->enable_node_cache == enable_node_cache) return; priv->enable_node_cache = enable_node_cache; /* if disabling the cache, destroy any existing data structures; * if enabling it, create them lazily when the first entry is cached * (see xb_silo_create_node()) */ if (!enable_node_cache) { g_clear_pointer(&priv->nodes, g_hash_table_unref); } silo_notify(self, obj_props[PROP_ENABLE_NODE_CACHE]); } /* private */ XbSiloProfileFlags xb_silo_get_profile_flags(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); return priv->profile_flags; } /* This will be invoked in silo->context */ static void xb_silo_watch_file_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { XbSilo *silo = XB_SILO(user_data); g_autofree gchar *fn = g_file_get_path(file); g_autofree gchar *basename = g_file_get_basename(file); if (g_str_has_prefix(basename, ".")) return; g_debug("%s changed, invalidating", fn); xb_silo_invalidate(silo); } typedef struct { XbSilo *silo; /* (owned) */ GFile *file; /* (owned) */ } WatchFileData; static void watch_file_data_free(WatchFileData *data) { g_clear_object(&data->silo); g_clear_object(&data->file); g_free(data); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(WatchFileData, watch_file_data_free) static gboolean watch_file_cb(gpointer user_data); /** * xb_silo_watch_file: * @self: a #XbSilo * @file: a #GFile * @cancellable: a #GCancellable, or %NULL * @error: the #GError, or %NULL * * Adds a file monitor to the silo. If the file or directory for @file changes * then the silo will be invalidated. * * The monitor will internally use the #GMainContext which was the thread * default when the #XbSilo was created, so that #GMainContext must be iterated * for monitoring to work. * * Returns: %TRUE for success, otherwise @error is set. * * Since: 0.1.0 **/ gboolean xb_silo_watch_file(XbSilo *self, GFile *file, GCancellable *cancellable, GError **error) { XbSiloPrivate *priv = GET_PRIVATE(self); g_autoptr(WatchFileData) data = NULL; g_return_val_if_fail(XB_IS_SILO(self), FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* return if cancelled; this is basically the only failure mode of * g_file_monitor() for local files, and this function shouldn’t really * be called on non-local files */ if (g_cancellable_set_error_if_cancelled(cancellable, error)) return FALSE; data = g_new0(WatchFileData, 1); data->silo = g_object_ref(self); data->file = g_object_ref(file); g_main_context_invoke(priv->context, watch_file_cb, g_steal_pointer(&data)); return TRUE; } static gboolean watch_file_cb(gpointer user_data) { g_autoptr(WatchFileData) data = g_steal_pointer(&user_data); XbSilo *self = data->silo; GFile *file = data->file; XbSiloFileMonitorItem *item; XbSiloPrivate *priv = GET_PRIVATE(self); g_autoptr(GFileMonitor) file_monitor = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->file_monitors_mutex); g_return_val_if_fail(locker != NULL, FALSE); /* already exists */ item = g_hash_table_lookup(priv->file_monitors, file); if (item != NULL) return G_SOURCE_REMOVE; /* try to create */ file_monitor = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, &error_local); if (file_monitor == NULL) { g_warning("Error adding file monitor: %s", error_local->message); return G_SOURCE_REMOVE; } g_file_monitor_set_rate_limit(file_monitor, 20); /* add */ item = g_slice_new0(XbSiloFileMonitorItem); item->file_monitor = g_object_ref(file_monitor); item->file_monitor_id = g_signal_connect(file_monitor, "changed", G_CALLBACK(xb_silo_watch_file_cb), self); g_hash_table_insert(priv->file_monitors, g_object_ref(file), item); return G_SOURCE_REMOVE; } /** * xb_silo_load_from_file: * @self: a #XbSilo * @file: a #GFile * @flags: #XbSiloLoadFlags, e.g. %XB_SILO_LOAD_FLAG_NONE * @cancellable: a #GCancellable, or %NULL * @error: the #GError, or %NULL * * Loads a silo from file. * * Returns: %TRUE for success, otherwise @error is set. * * Since: 0.1.0 **/ gboolean xb_silo_load_from_file(XbSilo *self, GFile *file, XbSiloLoadFlags flags, GCancellable *cancellable, GError **error) { XbSiloPrivate *priv = GET_PRIVATE(self); g_autofree gchar *fn = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GTimer) timer = xb_silo_start_profile(self); g_autoptr(GMutexLocker) file_monitors_locker = g_mutex_locker_new(&priv->file_monitors_mutex); g_return_val_if_fail(XB_IS_SILO(self), FALSE); g_return_val_if_fail(G_IS_FILE(file), FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* no longer valid (@nodes is cleared by xb_silo_load_from_bytes()) */ g_hash_table_remove_all(priv->file_monitors); g_clear_pointer(&file_monitors_locker, g_mutex_locker_free); g_hash_table_remove_all(priv->strtab_tags); g_clear_pointer(&priv->guid, g_free); g_clear_pointer(&priv->mmap, g_mapped_file_unref); fn = g_file_get_path(file); priv->mmap = g_mapped_file_new(fn, FALSE, error); if (priv->mmap == NULL) return FALSE; blob = g_mapped_file_get_bytes(priv->mmap); if (!xb_silo_load_from_bytes(self, blob, flags, error)) return FALSE; /* watch file for changes */ if (flags & XB_SILO_LOAD_FLAG_WATCH_BLOB) { if (!xb_silo_watch_file(self, file, cancellable, error)) return FALSE; } /* success */ xb_silo_add_profile(self, timer, "loaded file"); return TRUE; } /** * xb_silo_save_to_file: * @self: a #XbSilo * @file: a #GFile * @cancellable: a #GCancellable, or %NULL * @error: the #GError, or %NULL * * Saves a silo to a file. * * Returns: %TRUE for success, otherwise @error is set. * * Since: 0.1.0 **/ gboolean xb_silo_save_to_file(XbSilo *self, GFile *file, GCancellable *cancellable, GError **error) { XbSiloPrivate *priv = GET_PRIVATE(self); g_autoptr(GFile) file_parent = NULL; g_autoptr(GTimer) timer = xb_silo_start_profile(self); g_return_val_if_fail(XB_IS_SILO(self), FALSE); g_return_val_if_fail(G_IS_FILE(file), FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* invalid */ if (priv->data == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, "no data to save"); return FALSE; } /* ensure parent directories exist */ file_parent = g_file_get_parent(file); if (file_parent != NULL && !g_file_query_exists(file_parent, cancellable)) { if (!g_file_make_directory_with_parents(file_parent, cancellable, error)) return FALSE; } /* save and then rename */ if (!xb_file_set_contents(file, priv->data, (gsize)priv->datasz, cancellable, error)) return FALSE; xb_silo_add_profile(self, timer, "save file"); return TRUE; } /** * xb_silo_new_from_xml: * @xml: XML string * @error: the #GError, or %NULL * * Creates a new silo from an XML string. * * Returns: a new #XbSilo, or %NULL * * Since: 0.1.0 **/ XbSilo * xb_silo_new_from_xml(const gchar *xml, GError **error) { g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_return_val_if_fail(xml != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (!xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, error)) return NULL; xb_builder_import_source(builder, source); return xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); } /* private */ XbNode * xb_silo_create_node(XbSilo *self, XbSiloNode *sn, gboolean force_node_cache) { XbNode *n; XbSiloPrivate *priv = GET_PRIVATE(self); g_autoptr(GMutexLocker) locker = NULL; /* the cache should only be enabled/disabled before threads are * spawned, so `priv->enable_node_cache` can be accessed unlocked */ if (!priv->enable_node_cache && !force_node_cache) return xb_node_new(self, sn); locker = g_mutex_locker_new(&priv->nodes_mutex); /* ensure the cache exists */ if (priv->nodes == NULL) priv->nodes = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_object_unref); /* does already exist */ n = g_hash_table_lookup(priv->nodes, sn); if (n != NULL) return g_object_ref(n); /* create and add */ n = xb_node_new(self, sn); g_hash_table_insert(priv->nodes, sn, g_object_ref(n)); return n; } /* Push two opcodes onto the stack with appropriate rollback on failure. */ static gboolean _xb_stack_push_two(XbStack *opcodes, XbOpcode **op1, XbOpcode **op2, GError **error) { if (!xb_stack_push(opcodes, op1, error)) return FALSE; if (!xb_stack_push(opcodes, op2, error)) { xb_stack_pop(opcodes, NULL, NULL); return FALSE; } return TRUE; } /* convert [2] to position()=2 */ static gboolean xb_silo_machine_fixup_position_cb(XbMachine *self, XbStack *opcodes, gpointer user_data, GError **error) { XbOpcode *op1; XbOpcode *op2; XbOpcode *tail = xb_stack_peek_tail(opcodes); if (!_xb_stack_push_two(opcodes, &op1, &op2, error)) return FALSE; xb_machine_opcode_func_init(self, op1, "position"); xb_machine_opcode_func_init(self, op2, "eq"); /* always exists, but maybe a @level would be cleaner */ if (tail != NULL) { xb_opcode_set_level(op1, _xb_opcode_get_level(tail)); xb_opcode_set_level(op2, _xb_opcode_get_level(tail)); } return TRUE; } /* convert "'type' attr()" -> "'type' attr() '(null)' ne()" */ static gboolean xb_silo_machine_fixup_attr_exists_cb(XbMachine *self, XbStack *opcodes, gpointer user_data, GError **error) { XbOpcode *op1; XbOpcode *op2; XbOpcode *tail = xb_stack_peek_tail(opcodes); if (!_xb_stack_push_two(opcodes, &op1, &op2, error)) return FALSE; xb_opcode_text_init_static(op1, NULL); xb_machine_opcode_func_init(self, op2, "ne"); /* always exists, but maybe a @level would be cleaner */ if (tail != NULL) { xb_opcode_set_level(op1, _xb_opcode_get_level(tail)); xb_opcode_set_level(op2, _xb_opcode_get_level(tail)); } return TRUE; } static gboolean xb_silo_machine_fixup_attr_search_token_cb(XbMachine *self, XbStack *opcodes, gpointer user_data, GError **error) { XbOpcode op_func; XbOpcode op_text; XbOpcode op_search; XbOpcode *op_tmp; /* text() */ if (!xb_machine_stack_pop(self, opcodes, &op_func, error)) return FALSE; /* TEXT */ if (!xb_machine_stack_pop(self, opcodes, &op_text, error)) return FALSE; xb_machine_opcode_tokenize(self, &op_text); /* search() */ if (!xb_machine_stack_pop(self, opcodes, &op_search, error)) return FALSE; /* text() */ if (!xb_machine_stack_push(self, opcodes, &op_tmp, error)) return FALSE; *op_tmp = op_search; /* TEXT */ if (!xb_machine_stack_push(self, opcodes, &op_tmp, error)) return FALSE; *op_tmp = op_text; /* search() */ if (!xb_machine_stack_push(self, opcodes, &op_tmp, error)) return FALSE; *op_tmp = op_func; return TRUE; } static gboolean xb_silo_machine_func_attr_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { XbOpcode *op2; XbSiloNodeAttr *a; XbSilo *silo = XB_SILO(user_data); XbSiloQueryData *query_data = (XbSiloQueryData *)exec_data; const gchar *attr_value; g_auto(XbOpcode) op = XB_OPCODE_INIT(); /* optimize pass */ if (query_data == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED, "cannot optimize: no silo to query"); return FALSE; } if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* indexed string */ if (xb_opcode_get_kind(&op) == XB_OPCODE_KIND_INDEXED_TEXT) { guint32 val = xb_opcode_get_val(&op); a = xb_silo_node_get_attr_by_val(silo, query_data->sn, val); } else { const gchar *str = xb_opcode_get_str(&op); a = xb_silo_get_node_attr_by_str(silo, query_data->sn, str); } if (a == NULL) { return xb_machine_stack_push_text_static(self, stack, NULL, error); } if (!xb_machine_stack_push(self, stack, &op2, error)) return FALSE; attr_value = xb_silo_from_strtab(silo, a->attr_value, error); if (attr_value == NULL) return FALSE; xb_opcode_init(op2, XB_OPCODE_KIND_INDEXED_TEXT, attr_value, a->attr_value, NULL); return TRUE; } static gboolean xb_silo_machine_func_stem_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { XbSilo *silo = XB_SILO(user_data); XbOpcode *head; const gchar *str; g_auto(XbOpcode) op = XB_OPCODE_INIT(); head = xb_stack_peek_head(stack); if (head == NULL || !xb_opcode_cmp_str(head)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s type not supported", (head != NULL) ? xb_opcode_kind_to_string(xb_opcode_get_kind(head)) : "(null)"); return FALSE; } if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* TEXT */ str = xb_opcode_get_str(&op); return xb_machine_stack_push_text_steal(self, stack, xb_silo_stem(silo, str), error); } static gboolean xb_silo_machine_func_text_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { XbSilo *silo = XB_SILO(user_data); XbSiloQueryData *query_data = (XbSiloQueryData *)exec_data; XbOpcode *op; const gchar *text; guint8 token_count; /* optimize pass */ if (query_data == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED, "cannot optimize: no silo to query"); return FALSE; } if (xb_silo_node_get_text_idx(query_data->sn) != XB_SILO_UNSET) { text = xb_silo_from_strtab(silo, xb_silo_node_get_text_idx(query_data->sn), error); if (text == NULL) return FALSE; } else { text = ""; } if (!xb_machine_stack_push(self, stack, &op, error)) return FALSE; xb_opcode_init(op, XB_OPCODE_KIND_INDEXED_TEXT, text, xb_silo_node_get_text_idx(query_data->sn), NULL); /* use the fast token path even if there are no valid tokens */ if (xb_silo_node_has_flag(query_data->sn, XB_SILO_NODE_FLAG_IS_TOKENIZED)) xb_opcode_add_flag(op, XB_OPCODE_FLAG_TOKENIZED); /* add tokens */ token_count = xb_silo_node_get_token_count(query_data->sn); for (guint i = 0; i < token_count; i++) { guint32 stridx = xb_silo_node_get_token_idx(query_data->sn, i); const gchar *token = xb_silo_from_strtab(silo, stridx, error); if (token == NULL) return FALSE; xb_opcode_append_token(op, token); } return TRUE; } static gboolean xb_silo_machine_func_tail_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { XbSilo *silo = XB_SILO(user_data); XbSiloQueryData *query_data = (XbSiloQueryData *)exec_data; const gchar *tail; XbOpcode *op; /* optimize pass */ if (query_data == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED, "cannot optimize: no silo to query"); return FALSE; } if (xb_silo_node_get_tail_idx(query_data->sn) != XB_SILO_UNSET) { tail = xb_silo_from_strtab(silo, xb_silo_node_get_tail_idx(query_data->sn), error); if (tail == NULL) return FALSE; } else { tail = ""; } if (!xb_machine_stack_push(self, stack, &op, error)) return FALSE; xb_opcode_init(op, XB_OPCODE_KIND_INDEXED_TEXT, tail, xb_silo_node_get_tail_idx(query_data->sn), NULL); return TRUE; } static gboolean xb_silo_machine_func_first_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { XbSiloQueryData *query_data = (XbSiloQueryData *)exec_data; /* optimize pass */ if (query_data == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED, "cannot optimize: no silo to query"); return FALSE; } return xb_stack_push_bool(stack, query_data->position == 1, error); } static gboolean xb_silo_machine_func_last_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { XbSiloQueryData *query_data = (XbSiloQueryData *)exec_data; /* optimize pass */ if (query_data == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED, "cannot optimize: no silo to query"); return FALSE; } return xb_stack_push_bool(stack, query_data->sn->next == 0, error); } static gboolean xb_silo_machine_func_position_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { XbSiloQueryData *query_data = (XbSiloQueryData *)exec_data; /* optimize pass */ if (query_data == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED, "cannot optimize: no silo to query"); return FALSE; } return xb_machine_stack_push_integer(self, stack, query_data->position, error); } static gboolean xb_silo_machine_func_search_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { XbSilo *silo = XB_SILO(user_data); XbSiloPrivate *priv = GET_PRIVATE(silo); const gchar *text; const gchar *search; XbOpcode *head1 = NULL; XbOpcode *head2 = NULL; g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (xb_stack_get_size(stack) >= 2) { head1 = xb_stack_peek(stack, xb_stack_get_size(stack) - 1); head2 = xb_stack_peek(stack, xb_stack_get_size(stack) - 2); } if (head1 == NULL || !xb_opcode_cmp_str(head1) || head2 == NULL || !xb_opcode_cmp_str(head2)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s:%s types not supported", (head1 != NULL) ? xb_opcode_kind_to_string(xb_opcode_get_kind(head1)) : "(null)", (head2 != NULL) ? xb_opcode_kind_to_string(xb_opcode_get_kind(head2)) : "(null)"); return FALSE; } if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* this cannot be optimized away when constructing the query */ if (!_xb_opcode_has_flag(&op1, XB_OPCODE_FLAG_TOKENIZED) && _xb_opcode_get_kind(&op1) == XB_OPCODE_KIND_BOUND_TEXT) { xb_machine_opcode_tokenize(self, &op1); } if (!_xb_opcode_has_flag(&op2, XB_OPCODE_FLAG_TOKENIZED) && _xb_opcode_get_kind(&op2) == XB_OPCODE_KIND_BOUND_TEXT) { xb_machine_opcode_tokenize(self, &op2); } /* TOKN:TOKN */ if (xb_opcode_has_flag(&op1, XB_OPCODE_FLAG_TOKENIZED) && xb_opcode_has_flag(&op2, XB_OPCODE_FLAG_TOKENIZED)) { return xb_stack_push_bool( stack, xb_string_searchv(xb_opcode_get_tokens(&op2), xb_opcode_get_tokens(&op1)), error); } /* this is going to be slow, but correct */ text = xb_opcode_get_str(&op2); search = xb_opcode_get_str(&op1); if (text == NULL || search == NULL || text[0] == '\0' || search[0] == '\0') return xb_stack_push_bool(stack, FALSE, error); if (!g_str_is_ascii(text) || !g_str_is_ascii(search)) { if (priv->profile_flags & XB_SILO_PROFILE_FLAG_DEBUG) { g_debug("tokenization for [%s:%s] may be slow!", text, search); } return xb_stack_push_bool(stack, g_str_match_string(search, text, TRUE), error); } /* TEXT:TEXT */ return xb_stack_push_bool(stack, xb_string_search(text, search), error); } static gboolean xb_silo_machine_fixup_attr_text_cb(XbMachine *self, XbStack *opcodes, const gchar *text, gboolean *handled, gpointer user_data, GError **error) { /* @foo -> attr(foo) */ if (g_str_has_prefix(text, "@")) { XbOpcode *op1; XbOpcode *op2; if (!_xb_stack_push_two(opcodes, &op1, &op2, error)) return FALSE; xb_opcode_text_init(op1, text + 1); if (!xb_machine_opcode_func_init(self, op2, "attr")) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no attr opcode"); xb_stack_pop(opcodes, NULL, NULL); xb_stack_pop(opcodes, NULL, NULL); return FALSE; } *handled = TRUE; return TRUE; } /* not us */ return TRUE; } static void xb_silo_file_monitor_item_free(XbSiloFileMonitorItem *item) { g_file_monitor_cancel(item->file_monitor); g_signal_handler_disconnect(item->file_monitor, item->file_monitor_id); g_object_unref(item->file_monitor); g_slice_free(XbSiloFileMonitorItem, item); } static void xb_silo_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { XbSilo *self = XB_SILO(obj); XbSiloPrivate *priv = GET_PRIVATE(self); switch ((XbSiloProperty)prop_id) { case PROP_GUID: g_value_set_string(value, priv->guid); break; case PROP_VALID: g_value_set_boolean(value, priv->valid); break; case PROP_ENABLE_NODE_CACHE: g_value_set_boolean(value, priv->enable_node_cache); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } } static void xb_silo_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { XbSilo *self = XB_SILO(obj); XbSiloPrivate *priv = GET_PRIVATE(self); switch ((XbSiloProperty)prop_id) { case PROP_GUID: g_free(priv->guid); priv->guid = g_value_dup_string(value); silo_notify(self, obj_props[PROP_GUID]); break; case PROP_VALID: /* Read only */ g_assert_not_reached(); break; case PROP_ENABLE_NODE_CACHE: xb_silo_set_enable_node_cache(self, g_value_get_boolean(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } } static void xb_silo_init(XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE(self); priv->file_monitors = g_hash_table_new_full(g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, (GDestroyNotify)xb_silo_file_monitor_item_free); g_mutex_init(&priv->file_monitors_mutex); priv->strtab_tags = g_hash_table_new(g_str_hash, g_str_equal); priv->strindex = g_hash_table_new(g_str_hash, g_str_equal); priv->profile_str = g_string_new(NULL); priv->query_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref); g_rw_lock_init(&priv->query_cache_mutex); priv->nodes = NULL; /* initialised when first used */ g_mutex_init(&priv->nodes_mutex); priv->context = g_main_context_ref_thread_default(); #ifdef HAVE_LIBSTEMMER g_mutex_init(&priv->stemmer_mutex); #endif priv->machine = xb_machine_new(); xb_machine_add_method(priv->machine, "attr", 1, xb_silo_machine_func_attr_cb, self, NULL); xb_machine_add_method(priv->machine, "stem", 1, xb_silo_machine_func_stem_cb, self, NULL); xb_machine_add_method(priv->machine, "text", 0, xb_silo_machine_func_text_cb, self, NULL); xb_machine_add_method(priv->machine, "tail", 0, xb_silo_machine_func_tail_cb, self, NULL); xb_machine_add_method(priv->machine, "first", 0, xb_silo_machine_func_first_cb, self, NULL); xb_machine_add_method(priv->machine, "last", 0, xb_silo_machine_func_last_cb, self, NULL); xb_machine_add_method(priv->machine, "position", 0, xb_silo_machine_func_position_cb, self, NULL); xb_machine_add_method(priv->machine, "search", 2, xb_silo_machine_func_search_cb, self, NULL); xb_machine_add_operator(priv->machine, "~=", "search"); xb_machine_add_opcode_fixup(priv->machine, "INTE", xb_silo_machine_fixup_position_cb, self, NULL); xb_machine_add_opcode_fixup(priv->machine, "TEXT,FUNC:attr", xb_silo_machine_fixup_attr_exists_cb, self, NULL); xb_machine_add_opcode_fixup(priv->machine, "FUNC:text,TEXT,FUNC:search", xb_silo_machine_fixup_attr_search_token_cb, self, NULL); xb_machine_add_text_handler(priv->machine, xb_silo_machine_fixup_attr_text_cb, self, NULL); } static void xb_silo_finalize(GObject *obj) { XbSilo *self = XB_SILO(obj); XbSiloPrivate *priv = GET_PRIVATE(self); g_clear_pointer(&priv->nodes, g_hash_table_unref); g_mutex_clear(&priv->nodes_mutex); #ifdef HAVE_LIBSTEMMER if (priv->stemmer_ctx != NULL) sb_stemmer_delete(priv->stemmer_ctx); g_mutex_clear(&priv->stemmer_mutex); #endif g_clear_pointer(&priv->context, g_main_context_unref); g_free(priv->guid); g_string_free(priv->profile_str, TRUE); g_hash_table_unref(priv->query_cache); g_rw_lock_clear(&priv->query_cache_mutex); g_object_unref(priv->machine); g_hash_table_unref(priv->strindex); g_hash_table_unref(priv->file_monitors); g_mutex_clear(&priv->file_monitors_mutex); g_hash_table_unref(priv->strtab_tags); if (priv->mmap != NULL) g_mapped_file_unref(priv->mmap); if (priv->blob != NULL) g_bytes_unref(priv->blob); G_OBJECT_CLASS(xb_silo_parent_class)->finalize(obj); } static void xb_silo_class_init(XbSiloClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = xb_silo_finalize; object_class->get_property = xb_silo_get_property; object_class->set_property = xb_silo_set_property; /** * XbSilo:guid: */ obj_props[PROP_GUID] = g_param_spec_string("guid", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); /** * XbSilo:valid: */ obj_props[PROP_VALID] = g_param_spec_boolean("valid", NULL, NULL, TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); /** * XbSilo:enable-node-cache: * * Whether to cache all #XbNode instances ever constructed in a single * cache in the #XbSilo, so that the same #XbNode instance is always * returned in query results for a given XPath. This is a form of * memoisation, and allows xb_node_get_data() and xb_node_set_data() to * be used. * * This is enabled by default to preserve compatibility with older * versions of libxmlb, but most clients will want to disable it. It * adds a large memory overhead (no #XbNode is ever finalised) but * achieves moderately low hit rates for typical XML parsing workloads * where most nodes are accessed only once or twice as they are * processed and then processing moves on to other nodes. * * This property can only be changed before the #XbSilo is passed * between threads. Changing it is not thread-safe. * * Since: 0.2.0 */ obj_props[PROP_ENABLE_NODE_CACHE] = g_param_spec_boolean( "enable-node-cache", NULL, NULL, TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties(object_class, G_N_ELEMENTS(obj_props), obj_props); } /** * xb_silo_new: * * Creates a new silo. * * Returns: a new #XbSilo * * Since: 0.1.0 **/ XbSilo * xb_silo_new(void) { return g_object_new(XB_TYPE_SILO, NULL); } /** * xb_silo_lookup_query: * @self: an #XbSilo * @xpath: an XPath query string * * Create an #XbQuery from the given @xpath XPath string, or return it from the * query cache in the #XbSilo. * * @xpath must be valid: it is a programmer error if creating the query fails * (i.e. if xb_query_new() returns an error). * * This function is thread-safe. * * Returns: (transfer full): an #XbQuery representing @xpath * Since: 0.3.0 */ XbQuery * xb_silo_lookup_query(XbSilo *self, const gchar *xpath) { XbSiloPrivate *priv = GET_PRIVATE(self); XbQuery *result; g_rw_lock_reader_lock(&priv->query_cache_mutex); result = g_hash_table_lookup(priv->query_cache, xpath); g_rw_lock_reader_unlock(&priv->query_cache_mutex); if (result != NULL) { g_object_ref(result); } else { g_autoptr(XbQuery) query = NULL; /* check again with an exclusive lock */ g_rw_lock_writer_lock(&priv->query_cache_mutex); result = g_hash_table_lookup(priv->query_cache, xpath); if (result != NULL) { g_object_ref(result); } else { g_autoptr(GError) error_local = NULL; query = xb_query_new(self, xpath, &error_local); if (query == NULL) { /* This should not happen: the caller should * have written a valid query. */ g_error("Invalid XPath query ‘%s’: %s", xpath, error_local->message); g_rw_lock_writer_unlock(&priv->query_cache_mutex); g_assert_not_reached(); return NULL; } result = g_object_ref(query); g_hash_table_insert(priv->query_cache, g_strdup(xpath), g_steal_pointer(&query)); g_debug( "Caching query ‘%s’ (%p) in silo %p; query cache now has %u entries", xpath, query, self, g_hash_table_size(priv->query_cache)); } g_rw_lock_writer_unlock(&priv->query_cache_mutex); } return result; } libxmlb-0.3.22/src/xb-silo.h000066400000000000000000000065751476425255200155750ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include #include #include "xb-node.h" G_BEGIN_DECLS #define XB_TYPE_SILO (xb_silo_get_type()) G_DECLARE_DERIVABLE_TYPE(XbSilo, xb_silo, XB, SILO, GObject) struct _XbSiloClass { GObjectClass parent_class; /*< private >*/ void (*_xb_reserved1)(void); void (*_xb_reserved2)(void); void (*_xb_reserved3)(void); void (*_xb_reserved4)(void); void (*_xb_reserved5)(void); void (*_xb_reserved6)(void); void (*_xb_reserved7)(void); }; /** * XbSiloLoadFlags: * @XB_SILO_LOAD_FLAG_NONE: No extra flags to use * @XB_SILO_LOAD_FLAG_NO_MAGIC: No not check header signature * @XB_SILO_LOAD_FLAG_WATCH_BLOB: Watch the XMLB file for changes * * The flags for loading a silo. **/ typedef enum { XB_SILO_LOAD_FLAG_NONE = 0, /* Since: 0.1.0 */ XB_SILO_LOAD_FLAG_NO_MAGIC = 1 << 0, /* Since: 0.1.0 */ XB_SILO_LOAD_FLAG_WATCH_BLOB = 1 << 1, /* Since: 0.1.0 */ /*< private >*/ XB_SILO_LOAD_FLAG_LAST } XbSiloLoadFlags; /** * XbSiloProfileFlags: * @XB_SILO_PROFILE_FLAG_NONE: No extra flags to use * @XB_SILO_PROFILE_FLAG_DEBUG: Output profiling as debug * @XB_SILO_PROFILE_FLAG_APPEND: Save profiling in an appended string * @XB_SILO_PROFILE_FLAG_XPATH: Save XPATH queries * @XB_SILO_PROFILE_FLAG_OPTIMIZER: Output the machine optimizer as debug * * The flags used when profiling a silo. **/ typedef enum { XB_SILO_PROFILE_FLAG_NONE = 0, /* Since: 0.1.1 */ XB_SILO_PROFILE_FLAG_DEBUG = 1 << 0, /* Since: 0.1.1 */ XB_SILO_PROFILE_FLAG_APPEND = 1 << 1, /* Since: 0.1.1 */ XB_SILO_PROFILE_FLAG_XPATH = 1 << 2, /* Since: 0.1.1 */ XB_SILO_PROFILE_FLAG_OPTIMIZER = 1 << 3, /* Since: 0.3.1 */ /*< private >*/ XB_SILO_PROFILE_FLAG_LAST } XbSiloProfileFlags; XbSilo * xb_silo_new(void); XbSilo * xb_silo_new_from_xml(const gchar *xml, GError **error) G_GNUC_NON_NULL(1); GBytes * xb_silo_get_bytes(XbSilo *self) G_GNUC_NON_NULL(1); gboolean xb_silo_load_from_bytes(XbSilo *self, GBytes *blob, XbSiloLoadFlags flags, GError **error) G_GNUC_NON_NULL(1, 2); gboolean xb_silo_load_from_file(XbSilo *self, GFile *file, XbSiloLoadFlags flags, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); gboolean xb_silo_save_to_file(XbSilo *self, GFile *file, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); gchar * xb_silo_to_string(XbSilo *self, GError **error) G_GNUC_NON_NULL(1); guint xb_silo_get_size(XbSilo *self) G_GNUC_NON_NULL(1); const gchar * xb_silo_get_guid(XbSilo *self) G_GNUC_NON_NULL(1); XbNode * xb_silo_get_root(XbSilo *self) G_GNUC_NON_NULL(1); void xb_silo_invalidate(XbSilo *self) G_GNUC_NON_NULL(1); gboolean xb_silo_is_valid(XbSilo *self) G_GNUC_NON_NULL(1); gboolean xb_silo_watch_file(XbSilo *self, GFile *file, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); void xb_silo_set_profile_flags(XbSilo *self, XbSiloProfileFlags profile_flags) G_GNUC_NON_NULL(1); const gchar * xb_silo_get_profile_string(XbSilo *self) G_GNUC_NON_NULL(1); gboolean xb_silo_get_enable_node_cache(XbSilo *self) G_GNUC_NON_NULL(1); void xb_silo_set_enable_node_cache(XbSilo *self, gboolean enable_node_cache) G_GNUC_NON_NULL(1); #include "xb-query.h" XbQuery * xb_silo_lookup_query(XbSilo *self, const gchar *xpath) G_GNUC_NON_NULL(1, 2); G_END_DECLS libxmlb-0.3.22/src/xb-stack-private.h000066400000000000000000000055441476425255200173770ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "xb-opcode-private.h" #include "xb-stack.h" G_BEGIN_DECLS /* Members of this struct should not be accessed directly — use the accessor * functions below instead. */ struct _XbStack { /*< private >*/ gint ref; gboolean stack_allocated; /* whether this XbStack was allocated with alloca() */ guint pos; /* index of the next unused entry in .opcodes */ guint max_size; XbOpcode opcodes[]; /* allocated as part of XbStack */ }; /** * xb_stack_new_inline: * @max_stack_size: maximum size of the stack * * Creates a stack for the XbMachine request. Only #XbOpcodes can be pushed and * popped from the stack. * * The stack will be allocated on the current C stack frame, so @max_stack_size * should be chosen to not overflow the C process’ stack. * * Returns: (transfer full): a #XbStack * * Since: 0.3.1 **/ #define xb_stack_new_inline(max_stack_size) \ (G_GNUC_EXTENSION({ \ /* This function has to be static inline so we can use g_alloca(), which \ * is needed for performance reasons — about 3 million XbStacks are \ * allocated while starting gnome-software. */ \ guint xsni_max_size = (max_stack_size); \ XbStack *xsni_stack = \ g_alloca(sizeof(XbStack) + xsni_max_size * sizeof(XbOpcode)); \ xsni_stack->ref = 1; \ xsni_stack->stack_allocated = TRUE; \ xsni_stack->pos = 0; \ xsni_stack->max_size = xsni_max_size; \ (XbStack *)xsni_stack; \ })) XbStack * xb_stack_new(guint max_size); void xb_stack_unref(XbStack *self) G_GNUC_NON_NULL(1); XbStack * xb_stack_ref(XbStack *self) G_GNUC_NON_NULL(1); guint xb_stack_get_size(XbStack *self) G_GNUC_NON_NULL(1); guint xb_stack_get_max_size(XbStack *self) G_GNUC_NON_NULL(1); gboolean xb_stack_pop_two(XbStack *self, XbOpcode *opcode1_out, XbOpcode *opcode2_out, GError **error) G_GNUC_NON_NULL(1); gboolean xb_stack_push_bool(XbStack *self, gboolean val, GError **error) G_GNUC_NON_NULL(1); XbOpcode * xb_stack_peek(XbStack *self, guint idx) G_GNUC_NON_NULL(1); XbOpcode * xb_stack_peek_head(XbStack *self) G_GNUC_NON_NULL(1); XbOpcode * xb_stack_peek_tail(XbStack *self) G_GNUC_NON_NULL(1); G_DEFINE_AUTOPTR_CLEANUP_FUNC(XbStack, xb_stack_unref) G_END_DECLS libxmlb-0.3.22/src/xb-stack.c000066400000000000000000000124261476425255200157170ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbMachine" #include "config.h" #include #include "xb-opcode-private.h" #include "xb-stack-private.h" /** * xb_stack_unref: * @self: a #XbStack * * Decrements the reference count of the stack, freeing the object when the * refcount drops to zero. * * Since: 0.1.3 **/ void xb_stack_unref(XbStack *self) { g_assert(self->ref > 0); if (G_UNLIKELY(--self->ref > 0)) return; for (guint i = 0; i < self->pos; i++) xb_opcode_clear(&self->opcodes[i]); if (!self->stack_allocated) g_free(self); } /** * xb_stack_ref: * @self: a #XbStack * * Increments the refcount of the stack. * * Returns: (transfer none): the original @self #XbStack instance * * Since: 0.1.3 **/ XbStack * xb_stack_ref(XbStack *self) { self->ref++; return self; } /** * xb_stack_pop: * @self: a #XbStack * @opcode_out: (out caller-allocates) (optional): return location for the popped #XbOpcode * @error: a #GError, or %NULL * * Pops an opcode off the stack. * * Returns: %TRUE if popping succeeded, %FALSE if the stack was empty already * * Since: 0.2.0 **/ gboolean xb_stack_pop(XbStack *self, XbOpcode *opcode_out, GError **error) { if (G_UNLIKELY(self->pos == 0)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "stack is empty"); return FALSE; } self->pos--; if (opcode_out != NULL) *opcode_out = self->opcodes[self->pos]; return TRUE; } /** * xb_stack_pop_two: (skip): **/ gboolean xb_stack_pop_two(XbStack *self, XbOpcode *opcode1_out, XbOpcode *opcode2_out, GError **error) { if (G_UNLIKELY(self->pos < 2)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "stack is not full enough"); return FALSE; } if (opcode1_out != NULL) *opcode1_out = self->opcodes[self->pos - 1]; if (opcode2_out != NULL) *opcode2_out = self->opcodes[self->pos - 2]; self->pos -= 2; return TRUE; } /** * xb_stack_peek: * @self: a #XbStack * @idx: index * * Peeks an opcode from the stack. * * Returns: (transfer none): a #XbOpcode * * Since: 0.1.3 **/ XbOpcode * xb_stack_peek(XbStack *self, guint idx) { if (G_UNLIKELY(idx >= self->pos)) return NULL; return &self->opcodes[idx]; } /* private */ gboolean xb_stack_push_bool(XbStack *self, gboolean val, GError **error) { XbOpcode *op; if (!xb_stack_push(self, &op, error)) return FALSE; xb_opcode_bool_init(op, val); return TRUE; } /* private */ XbOpcode * xb_stack_peek_head(XbStack *self) { if (self->pos == 0) return NULL; return &self->opcodes[0]; } /* private */ XbOpcode * xb_stack_peek_tail(XbStack *self) { if (self->pos == 0) return NULL; return &self->opcodes[self->pos - 1]; } /** * xb_stack_push: * @self: a #XbStack * @opcode_out: (out) (nullable): return location for the new #XbOpcode * @error: a #GError, or %NULL * * Pushes a new empty opcode onto the end of the stack. A pointer to the opcode * is returned in @opcode_out so that the caller can initialise it. This must be * done before the stack is next used as, for performance reasons, the newly * pushed opcode is not zero-initialised. * * Returns: %TRUE if a new empty opcode was returned, or %FALSE if the stack has * reached its maximum size * Since: 0.2.0 **/ gboolean xb_stack_push(XbStack *self, XbOpcode **opcode_out, GError **error) { if (G_UNLIKELY(self->pos >= self->max_size)) { *opcode_out = NULL; g_set_error(error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, "stack is already at maximum size of %u", self->max_size); return FALSE; } *opcode_out = &self->opcodes[self->pos++]; return TRUE; } /** * xb_stack_get_size: * @self: a #XbStack * * Gets the current size of the stack. * * Returns: integer, where 0 is "empty" * * Since: 0.1.3 **/ guint xb_stack_get_size(XbStack *self) { return self->pos; } /** * xb_stack_get_max_size: * @self: a #XbStack * * Gets the maximum size of the stack. * * Returns: integer * * Since: 0.1.3 **/ guint xb_stack_get_max_size(XbStack *self) { return self->max_size; } /** * xb_stack_to_string: * @self: a #XbStack * * Returns a string representing a stack. * * Returns: text * * Since: 0.1.4 **/ gchar * xb_stack_to_string(XbStack *self) { GString *str = g_string_new(NULL); for (guint i = 0; i < self->pos; i++) { g_autofree gchar *tmp = xb_opcode_to_string(&self->opcodes[i]); g_string_append_printf(str, "%s,", tmp); } if (str->len > 0) g_string_truncate(str, str->len - 1); return g_string_free(str, FALSE); } /** * xb_stack_new: * @max_size: maximum size of the stack * * Creates a stack for the XbMachine request. Only #XbOpcode's can be pushed and * popped from the stack. * * Unlike with xb_stack_new_inline(), this stack will be allocated on the heap. * * Returns: (transfer full): a #XbStack * * Since: 0.1.3 **/ XbStack * xb_stack_new(guint max_size) { XbStack *self = g_malloc(sizeof(XbStack) + max_size * sizeof(XbOpcode)); self->ref = 1; self->stack_allocated = FALSE; self->pos = 0; self->max_size = max_size; return self; } GType xb_stack_get_type(void) { static GType type = 0; if (G_UNLIKELY(!type)) { type = g_boxed_type_register_static("XbStack", (GBoxedCopyFunc)xb_stack_ref, (GBoxedFreeFunc)xb_stack_unref); } return type; } libxmlb-0.3.22/src/xb-stack.h000066400000000000000000000010161476425255200157150ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "xb-opcode.h" G_BEGIN_DECLS typedef struct _XbStack XbStack; GType xb_stack_get_type(void); gchar * xb_stack_to_string(XbStack *self) G_GNUC_NON_NULL(1); gboolean xb_stack_pop(XbStack *self, XbOpcode *opcode_out, GError **error) G_GNUC_NON_NULL(1); gboolean xb_stack_push(XbStack *self, XbOpcode **opcode_out, GError **error) G_GNUC_NON_NULL(1, 2); G_END_DECLS libxmlb-0.3.22/src/xb-string-private.h000066400000000000000000000016661476425255200176010ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "xb-string.h" G_BEGIN_DECLS guint xb_string_replace(GString *str, const gchar *search, const gchar *replace) G_GNUC_NON_NULL(1); gboolean xb_string_contains(const gchar *text, const gchar *search); gboolean xb_string_search(const gchar *text, const gchar *search); gboolean xb_string_searchv(const gchar **text, const gchar **search); gboolean xb_string_token_valid(const gchar *text); gchar * xb_string_xml_escape(const gchar *str); gboolean xb_string_isspace(const gchar *str, gssize strsz); typedef struct __attribute__((packed)) { guint32 tlo; guint16 tmi; guint16 thi; guint16 clo; guint8 nde[6]; } XbGuid; gchar * xb_guid_to_string(XbGuid *guid) G_GNUC_NON_NULL(1); void xb_guid_compute_for_data(XbGuid *out, const guint8 *buf, gsize bufsz) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-string.c000066400000000000000000000166271476425255200161270ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbSilo" #include "config.h" #include #include #include "xb-string-private.h" /** * xb_string_replace: (skip) * @str: The #GString to operate on * @search: The text to search for * @replace: The text to use for substitutions * * Performs multiple search and replace operations on the given string. * * Returns: the number of replacements done, or 0 if @search is not found. **/ guint xb_string_replace(GString *str, const gchar *search, const gchar *replace) { gchar *tmp; guint count = 0; gsize search_idx = 0; gsize replace_len; gsize search_len; g_return_val_if_fail(str != NULL, 0); g_return_val_if_fail(search != NULL, 0); g_return_val_if_fail(replace != NULL, 0); /* nothing to do */ if (str->len == 0) return 0; search_len = strlen(search); replace_len = strlen(replace); do { tmp = g_strstr_len(str->str + search_idx, -1, search); if (tmp == NULL) break; /* advance the counter in case @replace contains @search */ search_idx = (gsize)(tmp - str->str); /* reallocate the string if required */ if (search_len > replace_len) { g_string_erase(str, (gssize)search_idx, (gssize)(search_len - replace_len)); memcpy(tmp, replace, replace_len); } else if (search_len < replace_len) { g_string_insert_len(str, (gssize)search_idx, replace, (gssize)(replace_len - search_len)); /* we have to treat this specially as it could have * been reallocated when the insertion happened */ memcpy(str->str + search_idx, replace, replace_len); } else { /* just memcmp in the new string */ memcpy(tmp, replace, replace_len); } search_idx += replace_len; count++; } while (TRUE); return count; } /** * xb_string_append_union: * @xpath: The #GString to operate on * @fmt: The format string * @...: varargs for @fmt * * Appends an XPath query into the string, automatically adding the union * operator (`|`) if required. * * Since: 0.1.2 **/ void xb_string_append_union(GString *xpath, const gchar *fmt, ...) { va_list args; g_return_if_fail(xpath != NULL); g_return_if_fail(fmt != NULL); if (xpath->len > 0) g_string_append(xpath, "|"); va_start(args, fmt); #pragma clang diagnostic ignored "-Wformat-nonliteral" g_string_append_vprintf(xpath, fmt, args); #pragma clang diagnostic pop va_end(args); } /** * xb_string_contains: (skip) * @text: The source string * @search: The text to search for * * Searches for a substring match. * * Returns: %TRUE if the string @search is contained in @text. **/ gboolean xb_string_contains(const gchar *text, const gchar *search) { guint search_sz; guint text_sz; /* can't possibly match */ if (text == NULL || search == NULL) return FALSE; /* sanity check */ text_sz = strlen(text); search_sz = strlen(search); if (search_sz > text_sz) return FALSE; for (guint i = 0; i < text_sz - search_sz + 1; i++) { if (strncmp(text + i, search, search_sz) == 0) return TRUE; } return FALSE; } /** * xb_string_search: (skip) * @text: The source string * @search: The text to search for * * Searches for a fuzzy search match, ignoring search matches that are not at * the start of the token. * * For example, `foo` and `baz` would match `foobar baz` but `bar` would not * match the same string. * * Returns: %TRUE if the string @search is contained in @text. * * Since: 0.3.0 **/ gboolean xb_string_search(const gchar *text, const gchar *search) { guint search_sz; guint text_sz; gboolean is_sow = TRUE; /* can't possibly match */ if (text == NULL || text[0] == '\0') return FALSE; if (search == NULL || search[0] == '\0') return FALSE; /* sanity check */ text_sz = strlen(text); search_sz = strlen(search); if (search_sz > text_sz) return FALSE; for (guint i = 0; i < text_sz - search_sz + 1; i++) { if (!g_ascii_isalnum(text[i])) { is_sow = TRUE; continue; } if (!is_sow) continue; if (g_ascii_strncasecmp(text + i, search, search_sz) == 0) return TRUE; /* no longer the start of the word */ is_sow = FALSE; } return FALSE; } /** * xb_string_searchv: (skip) * @text: NULL-terminated source strings * @search: NULL-terminated text tokens to search for * * Searches for a fuzzy search match, ignoring search matches that are not at * the start of the token. * * For example `{"foo", "baz"}` would match with `{"wizz", "foobar"}` but * `{"baz"}` would not match the same set of strings. * * Returns: %TRUE if the string @search is contained in @text. * * Since: 0.3.0 **/ gboolean xb_string_searchv(const gchar **text, const gchar **search) { if (text == NULL || text[0] == NULL || text[0][0] == '\0') return FALSE; if (search == NULL || search[0] == NULL || search[0][0] == '\0') return FALSE; for (guint j = 0; text[j] != NULL; j++) { for (guint i = 0; search[i] != NULL; i++) { if (g_str_has_prefix(text[j], search[i])) return TRUE; } } return FALSE; } /** * xb_string_token_valid: (skip) * @text: The potential token * @see_also: xb_builder_node_tokenize_text(), xb_builder_node_get_tokens() * * This is typically used to eliminate tokens which are not useful for search * matching. * * For instance, tokens like `in` and `at` are less than three characters in * size and should be rejected. * * Returns: %TRUE if the token should be used for searching. **/ gboolean xb_string_token_valid(const gchar *text) { if (text == NULL) return FALSE; if (text[0] == '\0' || text[1] == '\0' || text[2] == '\0') return FALSE; return TRUE; } /** * xb_string_escape: * @str: string, e.g. `app/org.gnome.ghex/x86_64/stable` * * Escapes XPath control sequences such as newlines, tabs, and forward slashes. * * Returns: (transfer full): new string that is safe to use for queries * * Since: 0.1.2 **/ gchar * xb_string_escape(const gchar *str) { GString *tmp = g_string_new(str); xb_string_replace(tmp, "/", "\\/"); xb_string_replace(tmp, "\t", "\\t"); xb_string_replace(tmp, "\n", "\\n"); return g_string_free(tmp, FALSE); } gchar * xb_string_xml_escape(const gchar *str) { GString *tmp = g_string_new(str); xb_string_replace(tmp, "&", "&"); xb_string_replace(tmp, "<", "<"); xb_string_replace(tmp, ">", ">"); xb_string_replace(tmp, "\"", """); return g_string_free(tmp, FALSE); } /* private */ gboolean xb_string_isspace(const gchar *str, gssize strsz) { gsize strsz_safe; if (str == NULL) return TRUE; strsz_safe = strsz >= 0 ? (gsize)strsz : strlen(str); for (gsize i = 0; i < strsz_safe; i++) { if (!g_ascii_isspace(str[i])) return FALSE; } return TRUE; } void xb_guid_compute_for_data(XbGuid *out, const guint8 *buf, gsize bufsz) { guint8 buf_tmp[20] = {0x0}; gsize buf_tmpsz = sizeof(buf_tmp); g_autoptr(GChecksum) checksum = g_checksum_new(G_CHECKSUM_SHA1); if (buf != NULL && bufsz != 0) g_checksum_update(checksum, (const guchar *)buf, bufsz); g_checksum_get_digest(checksum, buf_tmp, &buf_tmpsz); memcpy(out, buf_tmp, sizeof(XbGuid)); } gchar * xb_guid_to_string(XbGuid *guid) { return g_strdup_printf("%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x", (guint)GUINT32_TO_BE(guid->tlo), (guint)GUINT16_TO_BE(guid->tmi), (guint)GUINT16_TO_BE(guid->thi), (guint)GUINT16_TO_BE(guid->clo), guid->nde[0], guid->nde[1], guid->nde[2], guid->nde[3], guid->nde[4], guid->nde[5]); } libxmlb-0.3.22/src/xb-string.h000066400000000000000000000005411476425255200161200ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-compile.h" G_BEGIN_DECLS void xb_string_append_union(GString *xpath, const gchar *fmt, ...) G_GNUC_PRINTF(2, 3) G_GNUC_NON_NULL(1); gchar * xb_string_escape(const gchar *str) G_GNUC_NON_NULL(1); G_END_DECLS libxmlb-0.3.22/src/xb-tool.1000066400000000000000000000011711476425255200155000ustar00rootroot00000000000000.\" Report problems in https://github.com/hughsie/libxmlb .TH man 1 "18 April 2022" @PACKAGE_VERSION@ "xb-tool man page" .SH NAME xb-tool \- standalone XMLb utility .SH SYNOPSIS xb-tool [CMD] .SH DESCRIPTION This tool allows creating, dumping and querying binary XML blobs. .PP Additionally \fBxb-tool\fR can be used to profile specfic tokenized queries. .SH OPTIONS The xb-tool command takes various options depending on the action. Run \fBxb-tool --help\fR for the full list. .SH EXIT STATUS Commands that successfully execute will return "0", otherwise "1". .SH BUGS No known bugs. .SH AUTHOR Richard Hughes (richard@hughsie.com) libxmlb-0.3.22/src/xb-tool.c000066400000000000000000000361611476425255200155710ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include #include #ifdef HAVE_GIO_UNIX #include #endif #include #include #include "xb-builder.h" #include "xb-node.h" #include "xb-silo-export.h" #include "xb-silo-query.h" typedef struct { GCancellable *cancellable; GMainLoop *loop; GPtrArray *cmd_array; gboolean force; gboolean wait; gboolean profile; gchar **tokenize; } XbToolPrivate; static void xb_tool_private_free(XbToolPrivate *priv) { if (priv == NULL) return; if (priv->cmd_array != NULL) g_ptr_array_unref(priv->cmd_array); g_main_loop_unref(priv->loop); g_object_unref(priv->cancellable); g_strfreev(priv->tokenize); g_free(priv); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(XbToolPrivate, xb_tool_private_free) #pragma clang diagnostic pop typedef gboolean (*FuUtilPrivateCb)(XbToolPrivate *util, gchar **values, GError **error); typedef struct { gchar *name; gchar *arguments; gchar *description; FuUtilPrivateCb callback; } FuUtilItem; static void xb_tool_item_free(FuUtilItem *item) { g_free(item->name); g_free(item->arguments); g_free(item->description); g_free(item); } static gint xb_tool_sort_command_name_cb(FuUtilItem **item1, FuUtilItem **item2) { return g_strcmp0((*item1)->name, (*item2)->name); } static void xb_tool_add(GPtrArray *array, const gchar *name, const gchar *arguments, const gchar *description, FuUtilPrivateCb callback) { g_auto(GStrv) names = NULL; g_return_if_fail(name != NULL); g_return_if_fail(description != NULL); g_return_if_fail(callback != NULL); /* add each one */ names = g_strsplit(name, ",", -1); for (guint i = 0; names[i] != NULL; i++) { FuUtilItem *item = g_new0(FuUtilItem, 1); item->name = g_strdup(names[i]); if (i == 0) { item->description = g_strdup(description); } else { /* TRANSLATORS: this is a command alias, e.g. 'get-devices' */ item->description = g_strdup_printf("Alias to %s", names[0]); } item->arguments = g_strdup(arguments); item->callback = callback; g_ptr_array_add(array, item); } } static void xb_tool_cancelled_cb(GCancellable *cancellable, gpointer user_data) { XbToolPrivate *priv = (XbToolPrivate *)user_data; g_print("Cancelled!\n"); g_main_loop_quit(priv->loop); } static gchar * xb_tool_get_descriptions(GPtrArray *array) { gsize len; const gsize max_len = 31; FuUtilItem *item; GString *string; /* print each command */ string = g_string_new(""); for (guint i = 0; i < array->len; i++) { item = g_ptr_array_index(array, i); g_string_append(string, " "); g_string_append(string, item->name); len = strlen(item->name) + 2; if (item->arguments != NULL) { g_string_append(string, " "); g_string_append(string, item->arguments); len += strlen(item->arguments) + 1; } if (len < max_len) { for (guint j = len; j < max_len + 1; j++) g_string_append_c(string, ' '); g_string_append(string, item->description); g_string_append_c(string, '\n'); } else { g_string_append_c(string, '\n'); for (guint j = 0; j < max_len + 1; j++) g_string_append_c(string, ' '); g_string_append(string, item->description); g_string_append_c(string, '\n'); } } /* remove trailing newline */ if (string->len > 0) g_string_set_size(string, string->len - 1); return g_string_free(string, FALSE); } static gboolean xb_tool_run(XbToolPrivate *priv, const gchar *command, gchar **values, GError **error) { /* find command */ for (guint i = 0; i < priv->cmd_array->len; i++) { FuUtilItem *item = g_ptr_array_index(priv->cmd_array, i); if (g_strcmp0(item->name, command) == 0) return item->callback(priv, values, error); } /* not found */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Command not found"); return FALSE; } static gboolean xb_tool_dump(XbToolPrivate *priv, gchar **values, GError **error) { XbSiloLoadFlags flags = XB_SILO_LOAD_FLAG_NONE; /* check args */ if (g_strv_length(values) < 1) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected " "FILENAME" " -- e.g. `example.xmlb`"); return FALSE; } /* don't check the magic to make fuzzing easier */ if (priv->force) flags |= XB_SILO_LOAD_FLAG_NO_MAGIC; /* load blobs */ for (guint i = 0; values[i] != NULL; i++) { g_autofree gchar *str = NULL; g_autoptr(GFile) file = g_file_new_for_path(values[i]); g_autoptr(XbSilo) silo = xb_silo_new(); if (!xb_silo_load_from_file(silo, file, flags, NULL, error)) return FALSE; str = xb_silo_to_string(silo, error); if (str == NULL) return FALSE; g_print("%s", str); } return TRUE; } static gboolean xb_tool_export(XbToolPrivate *priv, gchar **values, GError **error) { XbSiloLoadFlags flags = XB_SILO_LOAD_FLAG_NONE; /* check args */ if (g_strv_length(values) < 1) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected " "FILENAME" " -- e.g. `example.xmlb`"); return FALSE; } /* don't check the magic to make fuzzing easier */ if (priv->force) flags |= XB_SILO_LOAD_FLAG_NO_MAGIC; /* load blobs */ for (guint i = 0; values[i] != NULL; i++) { g_autofree gchar *str = NULL; g_autoptr(GFile) file = g_file_new_for_path(values[i]); g_autoptr(XbSilo) silo = xb_silo_new(); if (!xb_silo_load_from_file(silo, file, flags, NULL, error)) return FALSE; str = xb_silo_export( silo, XB_NODE_EXPORT_FLAG_ADD_HEADER | XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | XB_NODE_EXPORT_FLAG_FORMAT_INDENT | XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS, error); if (str == NULL) return FALSE; g_print("%s", str); } return TRUE; } static gboolean xb_tool_query(XbToolPrivate *priv, gchar **values, GError **error) { guint limit = 0; g_autoptr(GFile) file = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(XbQuery) query = NULL; g_autoptr(XbSilo) silo = xb_silo_new(); g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); /* check args */ if (g_strv_length(values) < 2) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected " "FILENAME QUERY [LIMIT]" " -- e.g. `example.xmlb`"); return FALSE; } /* load blob */ file = g_file_new_for_path(values[0]); if (priv->profile) { xb_silo_set_profile_flags(silo, XB_SILO_PROFILE_FLAG_OPTIMIZER | XB_SILO_PROFILE_FLAG_XPATH | XB_SILO_PROFILE_FLAG_APPEND); } if (!xb_silo_load_from_file(silo, file, XB_SILO_LOAD_FLAG_NONE, NULL, error)) return FALSE; /* parse optional limit */ if (g_strv_length(values) == 3) limit = g_ascii_strtoull(values[2], NULL, 10); xb_query_context_set_limit(&context, limit); /* query */ query = xb_query_new_full(silo, values[1], XB_QUERY_FLAG_OPTIMIZE, error); if (query == NULL) return FALSE; results = xb_silo_query_with_context(silo, query, &context, error); if (results == NULL) return FALSE; for (guint i = 0; i < results->len; i++) { XbNode *n = g_ptr_array_index(results, i); g_autofree gchar *xml = NULL; xml = xb_node_export(n, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | XB_NODE_EXPORT_FLAG_FORMAT_INDENT, error); if (xml == NULL) return FALSE; g_print("RESULT: %s\n", xml); } /* profile */ if (priv->profile) g_print("%s", xb_silo_get_profile_string(silo)); return TRUE; } static gboolean xb_tool_query_file(XbToolPrivate *priv, gchar **values, GError **error) { g_autoptr(GFile) file = NULL; g_autoptr(XbSilo) silo = xb_silo_new(); /* check args */ if (g_strv_length(values) < 2) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected " "FILENAME FILENAME"); return FALSE; } /* load blob */ file = g_file_new_for_path(values[0]); if (!xb_silo_load_from_file(silo, file, XB_SILO_LOAD_FLAG_NONE, NULL, error)) return FALSE; /* optionally load file */ for (guint i = 1; values[i] != NULL; i++) { g_autofree gchar *xpath = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(GError) error_local = NULL; /* load XPath from file */ if (!g_file_get_contents(values[i], &xpath, NULL, error)) return FALSE; g_strdelimit(xpath, "\n", '\0'); /* query */ results = xb_silo_query(silo, xpath, 0, &error_local); if (results == NULL) { g_print("FAILED: %s\n", error_local->message); continue; } for (guint j = 0; j < results->len; j++) { XbNode *n = g_ptr_array_index(results, j); g_autofree gchar *xml = NULL; xml = xb_node_export(n, XB_NODE_EXPORT_FLAG_NONE, error); if (xml == NULL) return FALSE; g_print("RESULT: %s\n", xml); } } /* profile */ if (priv->profile) g_print("%s", xb_silo_get_profile_string(silo)); return TRUE; } static void xb_tool_silo_invalidated_cb(XbSilo *silo, GParamSpec *pspec, gpointer user_data) { XbToolPrivate *priv = (XbToolPrivate *)user_data; g_main_loop_quit(priv->loop); } static gboolean xb_tool_builder_tokenize_cb(XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { XbToolPrivate *priv = (XbToolPrivate *)user_data; for (guint i = 0; priv->tokenize != NULL && priv->tokenize[i] != NULL; i++) { if (g_strcmp0(xb_builder_node_get_element(bn), priv->tokenize[i]) == 0) { xb_builder_node_tokenize_text(bn); break; } } return TRUE; } static gboolean xb_tool_compile(XbToolPrivate *priv, gchar **values, GError **error) { const gchar *const *locales = g_get_language_names(); g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbSilo) silo = NULL; g_autoptr(GFile) file_dst = NULL; /* check args */ if (g_strv_length(values) < 2) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid arguments, expected " "FILE-OUT FILE [FILE]" " -- e.g. `example.xmlb example.xml`"); return FALSE; } /* load file */ for (guint i = 0; locales[i] != NULL; i++) xb_builder_add_locale(builder, locales[i]); for (guint i = 1; values[i] != NULL; i++) { g_autoptr(GFile) file = g_file_new_for_path(values[i]); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); if (priv->tokenize != NULL) { g_autoptr(XbBuilderFixup) fixup = NULL; fixup = xb_builder_fixup_new("TextTokenize", xb_tool_builder_tokenize_cb, priv, NULL); xb_builder_source_add_fixup(source, fixup); } if (!xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_WATCH_FILE | XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT, NULL, error)) return FALSE; xb_builder_import_source(builder, source); } file_dst = g_file_new_for_path(values[0]); xb_builder_set_profile_flags(builder, priv->profile ? XB_SILO_PROFILE_FLAG_APPEND : XB_SILO_PROFILE_FLAG_NONE); silo = xb_builder_ensure(builder, file_dst, XB_BUILDER_COMPILE_FLAG_WATCH_BLOB | XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID | XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS, NULL, error); if (silo == NULL) return FALSE; /* wait for invalidation */ if (priv->wait) { g_print("Waiting for invalidation…\n"); g_signal_connect(silo, "notify::valid", G_CALLBACK(xb_tool_silo_invalidated_cb), priv); g_main_loop_run(priv->loop); } /* profile */ if (priv->profile) g_print("%s", xb_silo_get_profile_string(silo)); /* success */ return TRUE; } #ifdef HAVE_GIO_UNIX static gboolean xb_tool_sigint_cb(gpointer user_data) { XbToolPrivate *priv = (XbToolPrivate *)user_data; g_debug("Handling SIGINT"); g_cancellable_cancel(priv->cancellable); return FALSE; } #endif int main(int argc, char *argv[]) { gboolean ret; gboolean verbose = FALSE; g_autofree gchar *cmd_descriptions = NULL; g_autoptr(XbToolPrivate) priv = g_new0(XbToolPrivate, 1); g_autoptr(GError) error = NULL; g_autoptr(GOptionContext) context = NULL; const GOptionEntry options[] = {{"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Print verbose debug statements", NULL}, {"force", 'v', 0, G_OPTION_ARG_NONE, &priv->force, "Force parsing of invalid files", NULL}, {"wait", 'w', 0, G_OPTION_ARG_NONE, &priv->wait, "Return only when the silo is no longer valid", NULL}, {"profile", 'p', 0, G_OPTION_ARG_NONE, &priv->profile, "Show profiling information", NULL}, {"tokenize", 't', 0, G_OPTION_ARG_STRING_ARRAY, &priv->tokenize, "Tokenize elements for faster search, e.g. name,summary", NULL}, {NULL}}; setlocale(LC_ALL, ""); /* do not let GIO start a session bus */ g_setenv("GIO_USE_VFS", "local", 1); /* add commands */ priv->cmd_array = g_ptr_array_new_with_free_func((GDestroyNotify)xb_tool_item_free); xb_tool_add(priv->cmd_array, "dump", "XMLBFILE", /* TRANSLATORS: command description */ "Dumps a XMLb file", xb_tool_dump); xb_tool_add(priv->cmd_array, "export", "XMLFILE", /* TRANSLATORS: command description */ "Exports a XMLb file", xb_tool_export); xb_tool_add(priv->cmd_array, "query", "XMLBFILE XPATH [LIMIT]", /* TRANSLATORS: command description */ "Queries a XMLb file", xb_tool_query); xb_tool_add(priv->cmd_array, "query-file", "XMLBFILE [FILE] [FILE]", /* TRANSLATORS: command description */ "Queries a XMLb file using an external XPath query", xb_tool_query_file); xb_tool_add(priv->cmd_array, "compile", "XMLBFILE XMLFILE [XMLFILE]", /* TRANSLATORS: command description */ "Compile XML to XMLb", xb_tool_compile); /* do stuff on ctrl+c */ priv->loop = g_main_loop_new(NULL, FALSE); priv->cancellable = g_cancellable_new(); g_signal_connect(priv->cancellable, "cancelled", G_CALLBACK(xb_tool_cancelled_cb), priv); #ifdef HAVE_GIO_UNIX g_unix_signal_add_full(G_PRIORITY_DEFAULT, SIGINT, xb_tool_sigint_cb, priv, NULL); #endif /* sort by command name */ g_ptr_array_sort(priv->cmd_array, (GCompareFunc)xb_tool_sort_command_name_cb); /* get a list of the commands */ context = g_option_context_new(NULL); cmd_descriptions = xb_tool_get_descriptions(priv->cmd_array); g_option_context_set_summary(context, cmd_descriptions); /* TRANSLATORS: DFU stands for device firmware update */ g_set_application_name("Binary XML Utility"); g_option_context_add_main_entries(context, options, NULL); ret = g_option_context_parse(context, &argc, &argv, &error); if (!ret) { g_print("%s: %s\n", "Failed to parse arguments", error->message); return EXIT_FAILURE; } /* set verbose? */ if (verbose) g_setenv("G_MESSAGES_DEBUG", "all", FALSE); /* run the specified command */ ret = xb_tool_run(priv, argv[1], (gchar **)&argv[2], &error); if (!ret) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_FAILED)) { g_autofree gchar *tmp = NULL; tmp = g_option_context_get_help(context, TRUE, NULL); g_print("%s\n\n%s", error->message, tmp); } else { g_print("%s\n", error->message); } return EXIT_FAILURE; } /* success/ */ return EXIT_SUCCESS; } libxmlb-0.3.22/src/xb-value-bindings-private.h000066400000000000000000000006011476425255200211660ustar00rootroot00000000000000/* * Copyright 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "xb-silo.h" #include "xb-value-bindings.h" gchar * xb_value_bindings_to_string(XbValueBindings *self) G_GNUC_NON_NULL(1); gboolean xb_value_bindings_indexed_text_lookup(XbValueBindings *self, XbSilo *silo, GError **error) G_GNUC_NON_NULL(1, 2); libxmlb-0.3.22/src/xb-value-bindings.c000066400000000000000000000252351476425255200175230ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * vi:set noexpandtab tabstop=8 shiftwidth=8: * * Copyright 2020 Endless OS Foundation LLC * * Author: Philip Withnall * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbValueBindings" #include "config.h" #include #include "xb-opcode-private.h" #include "xb-silo-private.h" #include "xb-value-bindings-private.h" typedef struct { guint8 kind; /* XbBoundValueKind */ guint32 val; gpointer ptr; GDestroyNotify destroy_func; } XbBoundValue; typedef enum { XB_BOUND_VALUE_KIND_NONE, XB_BOUND_VALUE_KIND_TEXT, XB_BOUND_VALUE_KIND_INTEGER, XB_BOUND_VALUE_KIND_INDEXED_TEXT, } XbBoundValueKind; typedef struct { /* Currently limited to 4 values since that’s all that any client * uses. This could be expanded to dynamically allow more in future. */ XbBoundValue values[4]; gpointer dummy[3]; } RealValueBindings; G_DEFINE_BOXED_TYPE(XbValueBindings, xb_value_bindings, xb_value_bindings_copy, xb_value_bindings_free) /** * xb_value_bindings_init: * @self: an uninitialised #XbValueBindings to initialise * * Initialise a stack-allocated #XbValueBindings struct so it can be used. * * Stack-allocated #XbValueBindings instances should be freed once finished * with, using xb_value_bindings_clear() (or `g_auto(XbValueBindings)`, which is * equivalent). * * Since: 0.3.0 */ void xb_value_bindings_init(XbValueBindings *self) { RealValueBindings *_self = (RealValueBindings *)self; for (gsize i = 0; i < G_N_ELEMENTS(_self->values); i++) _self->values[i].kind = XB_BOUND_VALUE_KIND_NONE; } static void xb_value_bindings_clear_index(XbValueBindings *self, guint idx) { RealValueBindings *_self = (RealValueBindings *)self; g_return_if_fail(idx < G_N_ELEMENTS(_self->values)); if (_self->values[idx].ptr != NULL && _self->values[idx].destroy_func) _self->values[idx].destroy_func(_self->values[idx].ptr); _self->values[idx].kind = XB_BOUND_VALUE_KIND_NONE; _self->values[idx].ptr = NULL; _self->values[idx].destroy_func = NULL; } /** * xb_value_bindings_clear: * @self: an #XbValueBindings * * Clear an #XbValueBindings, freeing any allocated memory it points to. * * After this function has been called, the contents of the #XbValueBindings are * undefined, and it’s only safe to call xb_value_bindings_init() on it. * * Since: 0.3.0 */ void xb_value_bindings_clear(XbValueBindings *self) { RealValueBindings *_self = (RealValueBindings *)self; for (gsize i = 0; i < G_N_ELEMENTS(_self->values); i++) xb_value_bindings_clear_index(self, i); } /* private */ gchar * xb_value_bindings_to_string(XbValueBindings *self) { RealValueBindings *_self = (RealValueBindings *)self; g_autoptr(GString) str = g_string_new(""); for (guint i = 0; i < G_N_ELEMENTS(_self->values); i++) { XbBoundValue *value = &_self->values[i]; if (value->kind == XB_BOUND_VALUE_KIND_NONE) continue; if (str->len > 0) g_string_append(str, ", "); if (value->kind == XB_BOUND_VALUE_KIND_INTEGER) g_string_append_printf(str, "?%u → %u", i, value->val); else if (value->kind == XB_BOUND_VALUE_KIND_TEXT && value->val > 0) g_string_append_printf(str, "?%u → %s [%u]", i, (const gchar *)value->ptr, value->val); else if (value->kind == XB_BOUND_VALUE_KIND_TEXT) g_string_append_printf(str, "?%u → %s", i, (const gchar *)value->ptr); } return g_string_free(g_steal_pointer(&str), FALSE); } /** * xb_value_bindings_copy: * @self: an #XbValueBindings * * Copy @self into a new heap-allocated #XbValueBindings instance. * * Returns: (transfer full): a copy of @self * Since: 0.3.0 */ XbValueBindings * xb_value_bindings_copy(XbValueBindings *self) { RealValueBindings *_self = (RealValueBindings *)self; g_autoptr(XbValueBindings) copy = g_new0(XbValueBindings, 1); xb_value_bindings_init(copy); for (gsize i = 0; i < G_N_ELEMENTS(_self->values); i++) { gboolean copied = xb_value_bindings_copy_binding(self, i, copy, i); g_assert(copied); } return g_steal_pointer(©); } /** * xb_value_bindings_free: * @self: a heap-allocated #XbValueBindings * * Free a heap-allocated #XbValueBindings instance. This should be used on * #XbValueBindings instances created with xb_value_bindings_copy(). * * For stack-allocated instances, xb_value_bindings_clear() should be used * instead. * * Since: 0.3.0 */ void xb_value_bindings_free(XbValueBindings *self) { g_return_if_fail(self != NULL); xb_value_bindings_clear(self); g_free(self); } /** * xb_value_bindings_is_bound: * @self: an #XbValueBindings * @idx: 0-based index of the binding to check * * Check whether a value has been bound to the given index using (for example) * xb_value_bindings_bind_str(). * * Returns: %TRUE if a value is bound to @idx, %FALSE otherwise * Since: 0.3.0 */ gboolean xb_value_bindings_is_bound(XbValueBindings *self, guint idx) { RealValueBindings *_self = (RealValueBindings *)self; return (idx < G_N_ELEMENTS(_self->values) && _self->values[idx].kind != XB_BOUND_VALUE_KIND_NONE); } /** * xb_value_bindings_bind_str: * @self: an #XbValueBindings * @idx: 0-based index to bind to * @str: (transfer full) (not nullable): a string to bind to @idx * @destroy_func: (nullable): function to free @str * * Bind @str to @idx in the value bindings. * * This will overwrite any previous binding at @idx. It will take ownership of * @str, and an appropriate @destroy_func must be provided to free @str once the * binding is no longer needed. @destroy_func will be called exactly once at * some point before the #XbValueBindings is cleared or freed. * * Since: 0.3.0 */ void xb_value_bindings_bind_str(XbValueBindings *self, guint idx, const gchar *str, GDestroyNotify destroy_func) { RealValueBindings *_self = (RealValueBindings *)self; g_return_if_fail(self != NULL); g_return_if_fail(str != NULL); /* Currently limited to two values, but this restriction could be lifted * in future. */ g_return_if_fail(idx < G_N_ELEMENTS(_self->values)); xb_value_bindings_clear_index(self, idx); _self->values[idx].kind = XB_BOUND_VALUE_KIND_TEXT; _self->values[idx].ptr = (gpointer)str; _self->values[idx].destroy_func = destroy_func; } /** * xb_value_bindings_bind_val: * @self: an #XbValueBindings * @idx: 0-based index to bind to * @val: an integer to bind to @idx * * Bind @val to @idx in the value bindings. * * This will overwrite any previous binding at @idx. * * Since: 0.3.0 */ void xb_value_bindings_bind_val(XbValueBindings *self, guint idx, guint32 val) { RealValueBindings *_self = (RealValueBindings *)self; g_return_if_fail(self != NULL); /* Currently limited to two values, but this restriction could be lifted * in future. */ g_return_if_fail(idx < G_N_ELEMENTS(_self->values)); xb_value_bindings_clear_index(self, idx); _self->values[idx].kind = XB_BOUND_VALUE_KIND_INTEGER; _self->values[idx].val = val; _self->values[idx].destroy_func = NULL; } /** * xb_value_bindings_lookup_opcode: * @self: an #XbValueBindings * @idx: 0-based index to look up the binding from * @opcode_out: (out caller-allocates) (not nullable): pointer to an #XbOpcode * to initialise from the binding * * Initialises an #XbOpcode with the value bound to @idx, if a value is bound. * If no value is bound, @opcode_out is not touched and %FALSE is returned. * * @opcode_out is initialised to point to the data inside the #XbValueBindings, * so must have a shorter lifetime than the #XbValueBindings. It will be of kind * %XB_OPCODE_KIND_BOUND_TEXT or %XB_OPCODE_KIND_BOUND_INTEGER. * * Returns: %TRUE if @idx was bound, %FALSE otherwise * Since: 0.3.0 */ gboolean xb_value_bindings_lookup_opcode(XbValueBindings *self, guint idx, XbOpcode *opcode_out) { RealValueBindings *_self = (RealValueBindings *)self; if (!xb_value_bindings_is_bound(self, idx)) return FALSE; switch (_self->values[idx].kind) { case XB_BOUND_VALUE_KIND_TEXT: xb_opcode_init(opcode_out, XB_OPCODE_KIND_BOUND_TEXT, _self->values[idx].ptr, 0, NULL); break; case XB_BOUND_VALUE_KIND_INTEGER: xb_opcode_init(opcode_out, XB_OPCODE_KIND_BOUND_INTEGER, NULL, _self->values[idx].val, NULL); break; case XB_BOUND_VALUE_KIND_INDEXED_TEXT: xb_opcode_init(opcode_out, XB_OPCODE_KIND_BOUND_INDEXED_TEXT, _self->values[idx].ptr, _self->values[idx].val, NULL); break; case XB_BOUND_VALUE_KIND_NONE: default: g_assert_not_reached(); } return TRUE; } /** * xb_value_bindings_copy_binding: * @self: an #XbValueBindings to copy from * @idx: 0-based index to look up the binding from in @self * @dest: an #XbValueBindings to copy to * @dest_idx: 0-based index to copy the binding to in @dest * * Copies the value bound at @idx on @self to @dest_idx on @dest. If no value is * bound at @idx, @dest is not modified and %FALSE is returned. * * @dest must be initialised. If a binding already exists at @dest_idx, it will * be overwritten. * * Returns: %TRUE if @idx was bound, %FALSE otherwise * Since: 0.3.0 */ gboolean xb_value_bindings_copy_binding(XbValueBindings *self, guint idx, XbValueBindings *dest, guint dest_idx) { RealValueBindings *_self = (RealValueBindings *)self; RealValueBindings *_dest = (RealValueBindings *)dest; if (!xb_value_bindings_is_bound(self, idx)) return FALSE; switch (_self->values[idx].kind) { case XB_BOUND_VALUE_KIND_TEXT: xb_value_bindings_bind_str(dest, dest_idx, _self->values[idx].ptr, NULL); break; case XB_BOUND_VALUE_KIND_INTEGER: xb_value_bindings_bind_val(dest, dest_idx, _self->values[idx].val); break; case XB_BOUND_VALUE_KIND_INDEXED_TEXT: xb_value_bindings_bind_str(dest, dest_idx, _self->values[idx].ptr, NULL); _dest->values[idx].kind = XB_BOUND_VALUE_KIND_INDEXED_TEXT; _dest->values[idx].val = _self->values[idx].val; break; case XB_BOUND_VALUE_KIND_NONE: default: g_assert_not_reached(); } return TRUE; } /* private */ gboolean xb_value_bindings_indexed_text_lookup(XbValueBindings *self, XbSilo *silo, GError **error) { RealValueBindings *_self = (RealValueBindings *)self; for (guint i = 0; i < G_N_ELEMENTS(_self->values); i++) { XbBoundValue *value = &_self->values[i]; if (value->kind == XB_BOUND_VALUE_KIND_TEXT) { guint32 val = xb_silo_strtab_index_lookup(silo, (const gchar *)value->ptr); if (val == XB_SILO_UNSET) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "indexed string '%s' was unfound", (const gchar *)value->ptr); return FALSE; } value->kind = XB_BOUND_VALUE_KIND_INDEXED_TEXT; value->val = val; } } return TRUE; } libxmlb-0.3.22/src/xb-value-bindings.h000066400000000000000000000053531476425255200175270ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * vi:set noexpandtab tabstop=8 shiftwidth=8: * * Copyright 2020 Endless OS Foundation LLC * * Author: Philip Withnall * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include #include "xb-opcode.h" G_BEGIN_DECLS /** * XbValueBindings: * * An opaque struct which contains values bound to a query. * * Since: 0.3.0 */ typedef struct { /*< private >*/ guint8 dummy0; guint32 dummy1; gpointer dummy2[2]; guint8 dummy3; guint32 dummy4; gpointer dummy5[2]; guint8 dummy6; guint32 dummy7; gpointer dummy8[2]; guint8 dummy9; guint32 dummy10; gpointer dummy11[2]; gpointer dummy12[3]; } XbValueBindings; GType xb_value_bindings_get_type(void); /** * XB_VALUE_BINDINGS_INIT: * * Static initialiser for #XbValueBindings so it can be used on the stack. * * Use it in association with g_auto(), to ensure the bindings are freed once * finished with: * |[ * g_auto(XbValueBindings) bindings = XB_VALUE_BINDINGS_INIT (); * * xb_value_bindings_bind_str (&bindings, 0, "test", NULL); * ]| * * Since: 0.3.0 */ #define XB_VALUE_BINDINGS_INIT() \ { \ 0, 0, {NULL, NULL}, 0, 0, {NULL, NULL}, 0, 0, {NULL, NULL}, 0, 0, {NULL, NULL}, \ { \ NULL, \ } \ } void xb_value_bindings_init(XbValueBindings *self) G_GNUC_NON_NULL(1); void xb_value_bindings_clear(XbValueBindings *self) G_GNUC_NON_NULL(1); G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(XbValueBindings, xb_value_bindings_clear) XbValueBindings * xb_value_bindings_copy(XbValueBindings *self) G_GNUC_NON_NULL(1); void xb_value_bindings_free(XbValueBindings *self) G_GNUC_NON_NULL(1); G_DEFINE_AUTOPTR_CLEANUP_FUNC(XbValueBindings, xb_value_bindings_free) gboolean xb_value_bindings_is_bound(XbValueBindings *self, guint idx) G_GNUC_NON_NULL(1); void xb_value_bindings_bind_str(XbValueBindings *self, guint idx, const gchar *str, GDestroyNotify destroy_func) G_GNUC_NON_NULL(1); void xb_value_bindings_bind_val(XbValueBindings *self, guint idx, guint32 val) G_GNUC_NON_NULL(1); gboolean xb_value_bindings_lookup_opcode(XbValueBindings *self, guint idx, XbOpcode *opcode_out) G_GNUC_NON_NULL(1, 3); gboolean xb_value_bindings_copy_binding(XbValueBindings *self, guint idx, XbValueBindings *dest, guint dest_idx) G_GNUC_NON_NULL(1, 3); G_END_DECLS libxmlb-0.3.22/src/xb-version.c000066400000000000000000000007201476425255200162710ustar00rootroot00000000000000/* * Copyright 2024 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "xb-version.h" /** * xb_version_string: * * Gets the XMLb installed runtime version. * * Returns: a version number, e.g. "0.3.19" * * Since: 0.3.19 **/ const gchar * xb_version_string(void) { return G_STRINGIFY(XMLB_MAJOR_VERSION) "." G_STRINGIFY(XMLB_MINOR_VERSION) "." G_STRINGIFY( XMLB_MICRO_VERSION); } libxmlb-0.3.22/src/xb-version.h.in000066400000000000000000000025721476425255200167120ustar00rootroot00000000000000/* * Copyright 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ /** * SECTION:xb-version * @short_description: Obtains the version for the installed libxmlb * * These compile time macros allow the user to enable parts of client code * depending on the version of libxmlb installed. */ #pragma once #include /** * XMLB_MAJOR_VERSION: * * The compile-time major version */ #ifndef XMLB_MAJOR_VERSION #define XMLB_MAJOR_VERSION (@XMLB_MAJOR_VERSION@) #endif /** * XMLB_MINOR_VERSION: * * The compile-time minor version */ #ifndef XMLB_MINOR_VERSION #define XMLB_MINOR_VERSION (@XMLB_MINOR_VERSION@) #endif /** * XMLB_MICRO_VERSION: * * The compile-time micro version */ #ifndef XMLB_MICRO_VERSION #define XMLB_MICRO_VERSION (@XMLB_MICRO_VERSION@) #endif /** * LIBXMLB_CHECK_VERSION: * @major: Major version number * @minor: Minor version number * @micro: Micro version number * * Check whether a libxmlb version equal to or greater than * major.minor.micro. */ #define LIBXMLB_CHECK_VERSION(major,minor,micro) \ (XMLB_MAJOR_VERSION > (major) || \ (XMLB_MAJOR_VERSION == (major) && XMLB_MINOR_VERSION > (minor)) || \ (XMLB_MAJOR_VERSION == (major) && XMLB_MINOR_VERSION == (minor) && \ XMLB_MICRO_VERSION >= (micro))) G_BEGIN_DECLS const gchar *xb_version_string (void); G_END_DECLS libxmlb-0.3.22/src/xb-zstd-decompressor.c000066400000000000000000000051171476425255200203000ustar00rootroot00000000000000/* * Copyright 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include #include #include "xb-zstd-decompressor.h" static void xb_zstd_decompressor_iface_init(GConverterIface *iface); struct _XbZstdDecompressor { GObject parent_instance; ZSTD_DStream *zstdstream; }; G_DEFINE_TYPE_WITH_CODE(XbZstdDecompressor, xb_zstd_decompressor, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE(G_TYPE_CONVERTER, xb_zstd_decompressor_iface_init)) static void xb_zstd_decompressor_finalize(GObject *object) { XbZstdDecompressor *self = XB_ZSTD_DECOMPRESSOR(object); ZSTD_freeDStream(self->zstdstream); G_OBJECT_CLASS(xb_zstd_decompressor_parent_class)->finalize(object); } static void xb_zstd_decompressor_init(XbZstdDecompressor *self) { } static void xb_zstd_decompressor_constructed(GObject *object) { XbZstdDecompressor *self = XB_ZSTD_DECOMPRESSOR(object); self->zstdstream = ZSTD_createDStream(); } static void xb_zstd_decompressor_class_init(XbZstdDecompressorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = xb_zstd_decompressor_finalize; object_class->constructed = xb_zstd_decompressor_constructed; } XbZstdDecompressor * xb_zstd_decompressor_new(void) { return g_object_new(XB_TYPE_ZSTD_DECOMPRESSOR, NULL); } static void xb_zstd_decompressor_reset(GConverter *converter) { XbZstdDecompressor *self = XB_ZSTD_DECOMPRESSOR(converter); ZSTD_initDStream(self->zstdstream); } static GConverterResult xb_zstd_decompressor_convert(GConverter *converter, const void *inbuf, gsize inbuf_size, void *outbuf, gsize outbuf_size, GConverterFlags flags, gsize *bytes_read, gsize *bytes_written, GError **error) { XbZstdDecompressor *self = XB_ZSTD_DECOMPRESSOR(converter); ZSTD_outBuffer output = { .dst = outbuf, .size = outbuf_size, .pos = 0, }; ZSTD_inBuffer input = { .src = inbuf, .size = inbuf_size, .pos = 0, }; size_t res; res = ZSTD_decompressStream(self->zstdstream, &output, &input); if (ZSTD_isError(res)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot decompress data: %s", ZSTD_getErrorName(res)); return G_CONVERTER_ERROR; } *bytes_read = input.pos; *bytes_written = output.pos; /* success */ return res == 0 ? G_CONVERTER_FINISHED : G_CONVERTER_CONVERTED; } static void xb_zstd_decompressor_iface_init(GConverterIface *iface) { iface->convert = xb_zstd_decompressor_convert; iface->reset = xb_zstd_decompressor_reset; } libxmlb-0.3.22/src/xb-zstd-decompressor.h000066400000000000000000000006151476425255200203030ustar00rootroot00000000000000/* * Copyright 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include G_BEGIN_DECLS #define XB_TYPE_ZSTD_DECOMPRESSOR (xb_zstd_decompressor_get_type()) G_DECLARE_FINAL_TYPE(XbZstdDecompressor, xb_zstd_decompressor, XB, ZSTD_DECOMPRESSOR, GObject) XbZstdDecompressor * xb_zstd_decompressor_new(void); G_END_DECLS libxmlb-0.3.22/src/xmlb.h000066400000000000000000000014451476425255200151510ustar00rootroot00000000000000/* * Copyright 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #define __LIBXMLB_H_INSIDE__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef __LIBXMLB_H_INSIDE__