From 914b9510294874e3e5b9296690c596e11367eb54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erkan=20I=C5=9EIK?= Date: Thu, 9 Oct 2025 23:47:03 +0300 Subject: [PATCH] python3 fork piksemel --- COPYING | 339 ++++++++ README.md | 202 +++++ hourstat.py | 24 + perftest.py | 34 + setup.py | 92 ++ src/autoxml.py | 383 +++++++++ src/iksemel.c | 1997 +++++++++++++++++++++++++++++++++++++++++++ src/iksemel.h | 407 +++++++++ src/pyiks.c | 1039 ++++++++++++++++++++++ tests/attributes.py | 14 + tests/generate.py | 94 ++ tests/iteration.py | 51 ++ tests/root.py | 26 + tests/traverse.py | 86 ++ 14 files changed, 4788 insertions(+) create mode 100644 COPYING create mode 100644 README.md create mode 100644 hourstat.py create mode 100644 perftest.py create mode 100644 setup.py create mode 100644 src/autoxml.py create mode 100644 src/iksemel.c create mode 100644 src/iksemel.h create mode 100644 src/pyiks.c create mode 100644 tests/attributes.py create mode 100644 tests/generate.py create mode 100644 tests/iteration.py create mode 100644 tests/root.py create mode 100644 tests/traverse.py diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 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. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +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 give any other recipients of the Program a copy of this License +along with the Program. + +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 Program or any portion +of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +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 Program, 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 Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) 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; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, 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 executable. However, as a +special exception, the source code 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. + +If distribution of executable or 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 counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program 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. + + 5. 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 Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program 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 to +this License. + + 7. 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 Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program 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 Program. + +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. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program 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. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 Program +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 Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, 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 + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), 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 Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. 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 program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c750b70 --- /dev/null +++ b/README.md @@ -0,0 +1,202 @@ +# Piksemel 2.0 + +Python XML API based on the iksemel library. +Copyright (C) 2005-2006 TUBITAK/UEKAE, 2019 Safa Arıman, 2020 Erdem Ersoy, 2022 Ersoy Kardesler + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2 of the License, or (at your +option) any later version. Please read the COPYING file. + +# Install + +You don't need anything other than an installed Python 3 framework (Python 3.6+). +iksemel-lite source is included and compiled built-in to the module. + +To compile and install: + +```bash +python3 setup.py install +``` + +Or using pip: + +```bash +pip3 install . +``` + +then you can import the piksemel module in your python programs. + +# Documentation + +## Module Constants + +* TAG +* ATTRIBUTE +* DATA + +These enumeration constants are used to report the type of the Node objects. + +## Module Exceptions + +### ParseError + +Raised when given string or file contains invalid XML. + +### NotTag + +Raised when a function accepting a tag node got another type of object. + +### NotData + +Raised when a function accepting a data node got another type of object. + +## Module Functions + +### parse(filename) + +Parses the given XML file, and returns the top level tag's node object. + +### parseString(string) + +Parses the given XML string and returns the top level tag's node object. + +### newDocument(tagname) + +Creates a new top level tag node object with given tag name. + +## Module Objects + +### Node + +This object represents a node of the XML document tree. If you iterate +over a node object, you'll get the list of its child nodes. + +## Node Object Methods + +### type() + +Returns the type of the node. + +### data() + +Returns character data for data nodes. + +### name() + +Returns the name of the tag for tag nodes. + +### attributes() + +Returns a list of node's attribute names. Returns an empty list +if there aren't any. + +### getAttribute(attributename) + +Returns the value of the given attribute for tag nodes. If such +an attribute doesn't exists, returns None. + +### setAttribute(attributename, value) + +Sets the given attribute's value for tag nodes. You can give +the value None for deleting an attribute. + +### getTag(tagname) + +Returns the first tag node child with the given name. If such a child +doesn't exists, returns None. + +### getTagData(tagname) + +Returns the contained character data from first tag node child with the +given name. + +### tags(tagname=None) + +Iterates over child tag nodes. If optional tagname is given, only +iterates over tags with the given name. + +### firstChild() + +Returns the first child node. + +### parent() + +Returns the parent node. + +### root() + +Returns the top level node. + +### next() + +Returns the next node. If this is the last sibling, returns None. + +### nextTag(tagname=None) + +Returns the next tag node. If optional tagname is given, returns the +next sibling tag node with the given name. Returns None if this is +the last such sibling. + +### previous() + +Returns the previous node. If this is the first sibling, returns None. + +### previousTag(tagname=None) + +Returns the previous tag node. If optional tagname is given, returns +the previous sibling tag node with the given name. Returns None if +this is the first such sibling. + +### insertTag(tagname) + +Inserts a new child tag node with given name to the child nodes. +Returns the newly created tag node object. + +### insertData(data) + +Inserts given character data to the child nodes. Returns the newly +created data node object. + +### setData(data) + +Deletes given tag's current child nodes, then inserts the data as +its child. + +### insertNode(node) + +Inserts given node tree to the child nodes. Returns the newly created +node object. + +### appendTag(tagname) + +Create a tag node with given name and append it as the next sibling. +Returns the newly created tag node object. + +### appendData(data) + +Appends given character data as the next sibling. Returns the newly +created data node object. + +### prependTag(tagname) + +Create a tag node with given name and append it as the previous +sibling. Returns the newly created tag node object. + +### prependData(data) + +Prepends given character data as the previous sibling. Returns the +newly created data node object. + +### hide() + +Deletes the node from document tree. + +### toString() + +Returns the XML string representation of node tree. + +### toPrettyString() + +Returns the XML string representation of node tree. Line feeds and +tabs are inserted into the document to make it look pretty. diff --git a/hourstat.py b/hourstat.py new file mode 100644 index 0000000..547581f --- /dev/null +++ b/hourstat.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +import piksemel +import sys + +def generate_hourly_stats(log, name=None): + hours = {} + total = 0 + for item in log.tags("logentry"): + if name and item.getTagData("author") != name: + pass + else: + hour = int(item.getTagData("date")[11:13]) + hours[hour] = hours.get(hour, 0) + 1 + total += 1 + for hour in range(0, 24): + print("%d: %d commit" % (hour, hours.get(hour, 0))) + print("total of %d commits" % total) + +doc = piksemel.parse(sys.argv[1]) +if len(sys.argv) == 3: + generate_hourly_stats(doc, sys.argv[2]) +else: + generate_hourly_stats(doc) diff --git a/perftest.py b/perftest.py new file mode 100644 index 0000000..3347db4 --- /dev/null +++ b/perftest.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +import sys +import piksemel as iks +import xml.dom.minidom as mini +from Ft.Xml.Domlette import NoExtDtdReader +import Ft.Lib +import timeit + +def piksor(name): + a = iks.parse(name) + total = 0 + for b in a.tags(): + size = b.getTagData("Size") + if size: + size = size.strip() + total += int(size) + +def cdom(name): + doc = NoExtDtdReader.parseUri(Ft.Lib.Uri.OsPathToUri(name)) + +def suxor(name): + a = mini.parse(name) + +if __name__ == "__main__": + name = sys.argv[1] + a = timeit.Timer('piksor("%s")' % name, "from __main__ import piksor") + b = timeit.Timer('cdom("%s")' % name, "from __main__ import cdom") + c = timeit.Timer('suxor("%s")' % name, "from __main__ import suxor") + print("piksemel", a.timeit(1)) + print("cdom", b.timeit(1)) + print("minidom", c.timeit(1)) + + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..91cd5b0 --- /dev/null +++ b/setup.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (C) 2006 TUBITAK/UEKAE, 2019 Safa Arıman, 2020 Erdem Ersoy +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. Please read the COPYING file. +# + +import sys +import os +import glob +import shutil +import subprocess +from setuptools import setup, Extension + +version = '2.0a1' + +distfiles = """ + README.md + setup.py + src/iksemel.c + src/iksemel.h + src/pyiks.c + tests/*.py +""" + +if 'sdist' in sys.argv: + distdir = "piksemel-%s" % version + filelist = [] + for t in distfiles.split(): + filelist.extend(glob.glob(t)) + if os.path.exists(distdir): + shutil.rmtree(distdir) + os.mkdir(distdir) + for file_ in filelist: + cum = distdir[:] + for d in os.path.dirname(file_).split('/'): + dn = os.path.join(cum, d) + cum = dn[:] + if not os.path.exists(dn): + os.mkdir(dn) + shutil.copy(file_, os.path.join(distdir, file_)) + subprocess.run(["tar", "czf", "piksemel-" + version + ".tar.gz", distdir]) + shutil.rmtree(distdir) + sys.exit(0) + +elif 'test' in sys.argv: + fail = 0 + for test in os.listdir("tests"): + if test.endswith(".py"): + if 0 != subprocess.call(["tests/" + test]): + fail += 1 + print(test, "failed!") + if not fail: + print("all tests passed :)") + sys.exit(0) + sys.exit(1) + + +setup( + name='piksemel', + version=version, + author='TUBITAK/UEKAE, Safa Arıman, Erdem Ersoy, Ersoy Kardesler', + description='Python XML API based on the iksemel library', + long_description=open('README.md').read(), + long_description_content_type='text/markdown', + ext_modules=[ + Extension( + 'piksemel', + sources=['src/iksemel.c', 'src/pyiks.c'], + extra_compile_args=["-fvisibility=default"] + ) + ], + python_requires='>=3.6', + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: C', + 'Topic :: Text Processing :: Markup :: XML', + ], +) diff --git a/src/autoxml.py b/src/autoxml.py new file mode 100644 index 0000000..e45eac7 --- /dev/null +++ b/src/autoxml.py @@ -0,0 +1,383 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (C) 2006-2007, TUBITAK/UEKAE +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. Please read the COPYING file. +# + +import piksemel +import inspect +import locale + +# Global counter for keeping tag order +_autoPiksCounter = 0 + +# Enumerations +multiple, optional = list(range(1, 3)) + + +class InvalidDocument(Exception): + """Validation error while reading XML data.""" + pass + + +class CharacterData: + """Maps character data of the XML node into a variable.""" + def parse(self, ctx, doc): + node = doc.firstChild() + if node.type() != piksemel.DATA or next(node) is not None: + ctx.error("this tag should only contain character data") + else: + ctx.use(self.varname, node.data()) + + def serialize(self, inst, doc): + doc.insertData(getattr(inst, self.varname)) + + +class Attribute: + """Maps an attribute of the XML node into a variable.""" + def __init__(self, attrname, *args): + self.name = attrname + self.is_optional = False + self.choices = None + for arg in args: + if arg == optional: + self.is_optional = True + elif isinstance(arg, (tuple, list)): + self.choices = arg + else: + raise TypeError("Unknown argument '%s'" % arg) + + def parse(self, ctx, doc): + value = doc.getAttribute(self.name) + if not self.is_optional and value == None: + ctx.error("required attribute '%s' is missing" % self.name) + if value and self.choices and not value in self.choices: + ctx.error("keyword '%s' is not accepted for attribute '%s'" % (value, self.name)) + ctx.use(self.varname, value) + + def serialize(self, inst, doc): + val = getattr(inst, self.varname) + if val: + doc.setAttribute(self.name, val) + + +class Tag: + """Maps a child tag's data of the XML node into a variable.""" + def __init__(self, tagname, *args): + global _autoPiksCounter + self.order = _autoPiksCounter + _autoPiksCounter += 1 + self.name = tagname + self.is_optional = False + self.is_multiple = False + self.sub = None + for arg in args: + if arg == optional: + self.is_optional = True + elif arg == multiple: + self.is_multiple = True + elif issubclass(arg, AutoXML): + self.sub = arg + else: + raise TypeError("Unknown argument '%s'" % arg) + + def parse(self, ctx, doc): + tags = list(doc.tags(self.name)) + if not self.is_optional and len(tags) == 0: + ctx.error("missing tag <%s>" % self.name) + return + if not self.is_multiple and len(tags) > 1: + ctx.error("tag <%s> should not appear more than once" % self.name) + return + old_doc = ctx.doc + vals = [] + for tag in tags: + ctx.doc = tag + if self.sub: + c = self.sub() + old_inst = ctx.inst + ctx.inst = c + c._autoPiksParse(ctx, tag) + ctx.inst = old_inst + vals.append(c) + else: + node = tag.firstChild() + if node.type() != piksemel.DATA or next(node) is not None: + ctx.error("this tag should only contain character data") + else: + vals.append(node.data()) + if not self.is_multiple: + if len(vals) > 0: + vals = vals[0] + else: + vals = None + ctx.use(self.varname, vals) + ctx.doc = old_doc + + def serialize(self, inst, doc): + vals = getattr(inst, self.varname) + if not vals: + return + if not self.is_multiple: + vals = [vals] + for val in vals: + tag = doc.insertTag(self.name) + if self.sub: + val.toString(tag) + else: + if val: + tag.insertData(val) + + +class TagCollection: + """Maps a collection of child tags of the XML node into a variable.""" + def __init__(self, tagname, kidname, *args): + global _autoPiksCounter + self.order = _autoPiksCounter + _autoPiksCounter += 1 + self.name = tagname + self.kidname = kidname + self.is_optional = False + self.sub = None + for arg in args: + if arg == optional: + self.is_optional = True + elif issubclass(arg, AutoXML): + self.sub = arg + else: + raise TypeError("Unknown argument '%s'" % arg) + + def parse(self, ctx, doc): + tags = list(doc.tags(self.name)) + if len(tags) == 0: + if not self.is_optional: + ctx.error("missing tag <%s>" % self.name) + ctx.use(self.varname, []) + return + if len(tags) > 1: + ctx.error("tag <%s> should not appear more than once" % self.name) + return + tags = list(tags[0].tags()) + old_doc = ctx.doc + vals = [] + for tag in tags: + ctx.doc = tag + if tag.name() != self.kidname: + ctx.error("this is a collection of <%s> tags, not <%s>" % (self.kidname, tag.name())) + if self.sub: + c = self.sub() + old_inst = ctx.inst + ctx.inst = c + c._autoPiksParse(ctx, tag) + ctx.inst = old_inst + vals.append(c) + else: + node = tag.firstChild() + if node.type() != piksemel.DATA or next(node) is not None: + ctx.error("this tag should only contain character data") + else: + vals.append(node.data()) + ctx.use(self.varname, vals) + ctx.doc = old_doc + + def serialize(self, inst, doc): + vals = getattr(inst, self.varname) + if not vals: + return + parent = doc.insertTag(self.name) + for val in vals: + tag = parent.insertTag(self.kidname) + if self.sub: + val.toString(tag) + else: + if val: + tag.insertData(val) + + +class LocalText(dict): + @staticmethod + def get_lang(): + lang, enc = locale.getlocale() + if not lang: + lang, enc = locale.getdefaultlocale() + if not lang: + return "en" + return lang[:2] + + def __str__(self): + lang = self.get_lang() + # Return text for current language + t = self.get(lang, None) + if t: + return t + # Fallback to English + t = self.get("en", None) + if t: + return t + # Fallback to anything + if len(self) > 0: + return self[0] + return str() + + +class TagLocalized: + """Maps the translated tags of the XML node into a dictionary variable.""" + def __init__(self, tagname, opt=None): + global _autoPiksCounter + self.order = _autoPiksCounter + _autoPiksCounter += 1 + self.name = tagname + self.is_optional = False + if opt == optional: + self.is_optional = True + + def parse(self, ctx, doc): + tags = list(doc.tags(self.name)) + if not self.is_optional and len(tags) == 0: + ctx.error("missing tag <%s>" % self.name) + return + val = LocalText() + for tag in tags: + lang = tag.getAttribute("xml:lang") + if not lang: + lang = "en" + node = tag.firstChild() + if node.type() != piksemel.DATA or next(node) is not None: + ctx.error("%s: Localized tag should only contain character data" % self.name) + else: + if lang in val: + ctx.error("%s: Duplicate translation for language %s" % (self.name, lang)) + data = node.data() + val[lang] = data + ctx.use(self.varname, val) + + def serialize(self, inst, doc): + val = getattr(inst, self.varname) + if not val: + return + langs = list(val.keys()) + langs.sort() + for lang in langs: + tag = doc.insertTag(self.name) + if lang != "en": + tag.setAttribute("xml:lang", lang) + tag.insertData(val[lang]) + + +class AutoPiksemelContext: + """Utility class for passing parameters between validation functions.""" + def __init__(self, inst): + self.inst = inst + self.errors = [] + self.doc = None + + def error(self, message): + path = [] + doc = self.doc + while doc: + path.append(doc.name()) + doc = doc.parent() + path.reverse() + self.errors.append("%s: %s" % ("/".join(path), message)) + + def use(self, varname, value): + setattr(self.inst, varname, value) + + +class AutoXML: + """Automatic XML <-> Class attributes converter with validation.""" + def __init__(self, root_tag=None, path=None, xmlstring=None): + self.root_tag = root_tag + doc = None + if path: + if xmlstring: + raise TypeError("Dont use both path and xmlstring in AutoPiksemel()") + doc = piksemel.parse(path) + elif xmlstring: + doc = piksemel.parseString(xmlstring) + if doc: + if doc.name() != root_tag: + err = "Root tag does not match: %s != %s" % (root_tag, doc.name()) + raise InvalidDocument(err) + ctx = AutoPiksemelContext(self) + ctx.doc = doc + self._autoPiksParse(ctx, doc) + if len(ctx.errors) > 0: + raise InvalidDocument("\n".join(ctx.errors)) + + def _autoPiksScan(self): + """Scan XML<->Class mappings and return indexed objects.""" + # Generated data is cached as a Class variable + defs = getattr(self.__class__, "_autoPiksDefs", None) + if not defs: + members = inspect.getmembers(self.__class__) + cdata = None + attrs = {} + tags = {} + for name, obj in members: + if isinstance(obj, CharacterData): + cdata = obj + obj.varname = name + elif isinstance(obj, Attribute): + attrs[obj.name] = obj + obj.varname = name + elif isinstance(obj, (Tag, TagCollection, TagLocalized)): + tags[obj.name] = obj + obj.varname = name + if cdata and len(tags) > 0: + raise TypeError("Class %s defined both CharacterData() and Tag()s" % self.__class__) + defs = (cdata, attrs, tags) + self.__class__._autoPiksDefs = defs + return defs + + def _autoPiksParse(self, ctx, doc): + """Convert XML data to class attributes.""" + cdata, attrs, tags = self._autoPiksScan() + # Check character data + if cdata: + cdata.parse(ctx, doc) + # Check attributes + for key in doc.attributes(): + if not key in attrs: + ctx.error("unknown attribute '%s'" % key) + for obj in list(attrs.values()): + obj.parse(ctx, doc) + # Check tags + for tag in doc.tags(): + if not tag.name() in tags: + ctx.error("unknown tag <%s>" % tag.name()) + for obj in list(tags.values()): + obj.parse(ctx, doc) + # Custom validation + if len(ctx.errors) == 0: + # Since validater functions access members without checking + # we dont call them if there is already an error. + validate_func = getattr(self, "validate", None) + if validate_func: + validate_func(ctx) + + def toString(self, doc=None): + if not doc: + doc = piksemel.newDocument(self.root_tag) + self.toString(doc) + return doc.toPrettyString() + + cdata, attrs, tags = self._autoPiksScan() + attrs = list(attrs.values()) + attrs.sort(key=lambda x: x.varname) + tags = list(tags.values()) + tags.sort(key=lambda x: x.order) + + for attr in attrs: + attr.serialize(self, doc) + + if cdata: + cdata.serialize(self, doc) + else: + for tag in tags: + tag.serialize(self, doc) diff --git a/src/iksemel.c b/src/iksemel.c new file mode 100644 index 0000000..d0c8e85 --- /dev/null +++ b/src/iksemel.c @@ -0,0 +1,1997 @@ +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2003 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +/* minimum sax buffer size */ +#define SAX_BUFFER_MIN_SIZE 128 + +/* sax parser structure plus extra data of dom parser */ +#define DEFAULT_DOM_CHUNK_SIZE 256 + +/* sax parser structure plus extra data of stream parser */ +#define DEFAULT_STREAM_CHUNK_SIZE 256 + +/* iks structure, its data, child iks structures, for stream parsing */ +#define DEFAULT_IKS_CHUNK_SIZE 1024 + +/* iks structure, its data, child iks structures, for file parsing */ +#define DEFAULT_DOM_IKS_CHUNK_SIZE 2048 + +/* rule structure and from/to/id/ns strings */ +#define DEFAULT_RULE_CHUNK_SIZE 128 + +/* file is read by blocks with this size */ +#define FILE_IO_BUF_SIZE 4096 + +/* network receive buffer */ +#define NET_IO_BUF_SIZE 4096 +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2003 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +#include +#include +#include +#include +#include "iksemel.h" + +/***** malloc wrapper *****/ + +static void *(*my_malloc_func)(size_t size); +static void (*my_free_func)(void *ptr); + +void * +iks_malloc (size_t size) +{ + if (my_malloc_func) + return my_malloc_func (size); + else + return malloc (size); +} + +void +iks_free (void *ptr) +{ + if (my_free_func) + my_free_func (ptr); + else + free (ptr); +} + +void +iks_set_mem_funcs (void *(*malloc_func)(size_t size), void (*free_func)(void *ptr)) +{ + my_malloc_func = malloc_func; + my_free_func = free_func; +} + +/***** NULL-safe Functions *****/ + +char * +iks_strdup (const char *src) +{ + if (src) return strdup(src); + return NULL; +} + +char * +iks_strcat (char *dest, const char *src) +{ + size_t len; + + if (!src) return dest; + + len = strlen (src); + memcpy (dest, src, len); + dest[len] = '\0'; + return dest + len; +} + +int +iks_strcmp (const char *a, const char *b) +{ + if (!a || !b) return -1; + return strcmp (a, b); +} + +int +iks_strcasecmp (const char *a, const char *b) +{ + if (!a || !b) return -1; + return strcasecmp (a, b); +} + +int +iks_strncmp (const char *a, const char *b, size_t n) +{ + if (!a || !b) return -1; + return strncmp (a, b, n); +} + +int +iks_strncasecmp (const char *a, const char *b, size_t n) +{ + if (!a || !b) return -1; + return strncasecmp (a, b, n); +} + +size_t +iks_strlen (const char *src) +{ + if (!src) return 0; + return strlen (src); +} + +/***** XML Escaping *****/ + +char * +iks_escape (ikstack *s, char *src, size_t len) +{ + char *ret; + int j; + size_t i, nlen; + + if (!src || !s) return NULL; + if (len == (size_t)-1) len = strlen (src); + + nlen = len; + for (i=0; i': nlen += 3; break; + case '\'': nlen += 5; break; + case '"': nlen += 5; break; + } + } + if (len == nlen) return src; + + ret = iks_stack_alloc (s, nlen + 1); + if (!ret) return NULL; + + for (i=j=0; i': memcpy (&ret[j], ">", 4); j += 4; break; + default: ret[j++] = src[i]; + } + } + ret[j] = '\0'; + + return ret; +} + +char * +iks_unescape (ikstack *s, char *src, size_t len) +{ + size_t i; + int j; + char *ret; + + if (!s || !src) return NULL; + if (!strchr (src, '&')) return src; + if (len == (size_t)-1) len = strlen (src); + + ret = iks_stack_alloc (s, len + 1); + if (!ret) return NULL; + + for (i=j=0; i +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +struct align_test { char a; double b; }; +#define DEFAULT_ALIGNMENT ((size_t) ((char *) &((struct align_test *) 0)->b - (char *) 0)) +#define ALIGN_MASK ( DEFAULT_ALIGNMENT - 1 ) +#define MIN_CHUNK_SIZE ( DEFAULT_ALIGNMENT * 8 ) +#define MIN_ALLOC_SIZE DEFAULT_ALIGNMENT +#define ALIGN(x) ( (x) + (DEFAULT_ALIGNMENT - ( (x) & ALIGN_MASK)) ) + +typedef struct ikschunk_struct { + struct ikschunk_struct *next; + size_t size; + size_t used; + size_t last; + char data[4]; +} ikschunk; + +struct ikstack_struct { + size_t allocated; + ikschunk *meta; + ikschunk *data; +}; + +static ikschunk * +find_space (ikstack *s, ikschunk *c, size_t size) +{ + /* FIXME: dont use *2 after over allocated chunks */ + while (1) { + if (c->size - c->used >= size) return c; + if (!c->next) { + if ((c->size * 2) > size) size = c->size * 2; + c->next = iks_malloc (sizeof (ikschunk) + size); + if (!c->next) return NULL; + s->allocated += sizeof (ikschunk) + size; + c = c->next; + c->next = NULL; + c->size = size; + c->used = 0; + c->last = (size_t) -1; + return c; + } + c = c->next; + } + return NULL; +} + +ikstack * +iks_stack_new (size_t meta_chunk, size_t data_chunk) +{ + ikstack *s; + size_t len; + + if (meta_chunk < MIN_CHUNK_SIZE) meta_chunk = MIN_CHUNK_SIZE; + if (meta_chunk & ALIGN_MASK) meta_chunk = ALIGN (meta_chunk); + if (data_chunk < MIN_CHUNK_SIZE) data_chunk = MIN_CHUNK_SIZE; + if (data_chunk & ALIGN_MASK) data_chunk = ALIGN (data_chunk); + + len = sizeof (ikstack) + meta_chunk + data_chunk + (sizeof (ikschunk) * 2); + s = iks_malloc (len); + if (!s) return NULL; + s->allocated = len; + s->meta = (ikschunk *) ((char *) s + sizeof (ikstack)); + s->meta->next = NULL; + s->meta->size = meta_chunk; + s->meta->used = 0; + s->meta->last = (size_t) -1; + s->data = (ikschunk *) ((char *) s + sizeof (ikstack) + sizeof (ikschunk) + meta_chunk); + s->data->next = NULL; + s->data->size = data_chunk; + s->data->used = 0; + s->data->last = (size_t) -1; + return s; +} + +void * +iks_stack_alloc (ikstack *s, size_t size) +{ + ikschunk *c; + void *mem; + + if (size < MIN_ALLOC_SIZE) size = MIN_ALLOC_SIZE; + if (size & ALIGN_MASK) size = ALIGN (size); + + c = find_space (s, s->meta, size); + if (!c) return NULL; + mem = c->data + c->used; + c->used += size; + return mem; +} + +char * +iks_stack_strdup (ikstack *s, const char *src, size_t len) +{ + ikschunk *c; + char *dest; + + if (!src) return NULL; + if (0 == len) len = strlen (src); + + c = find_space (s, s->data, len + 1); + if (!c) return NULL; + dest = c->data + c->used; + c->last = c->used; + c->used += len + 1; + memcpy (dest, src, len); + dest[len] = '\0'; + return dest; +} + +char * +iks_stack_strcat (ikstack *s, char *old, size_t old_len, const char *src, size_t src_len) +{ + char *ret; + ikschunk *c; + + if (!old) { + return iks_stack_strdup (s, src, src_len); + } + if (0 == old_len) old_len = strlen (old); + if (0 == src_len) src_len = strlen (src); + + for (c = s->data; c; c = c->next) { + if (c->data + c->last == old) break; + } + if (!c) { + c = find_space (s, s->data, old_len + src_len + 1); + if (!c) return NULL; + ret = c->data + c->used; + c->last = c->used; + c->used += old_len + src_len + 1; + memcpy (ret, old, old_len); + memcpy (ret + old_len, src, src_len); + ret[old_len + src_len] = '\0'; + return ret; + } + + if (c->size - c->used > src_len) { + ret = c->data + c->last; + memcpy (ret + old_len, src, src_len); + c->used += src_len; + ret[old_len + src_len] = '\0'; + } else { + /* FIXME: decrease c->used before moving string to new place */ + c = find_space (s, s->data, old_len + src_len + 1); + if (!c) return NULL; + c->last = c->used; + ret = c->data + c->used; + memcpy (ret, old, old_len); + c->used += old_len; + memcpy (c->data + c->used, src, src_len); + c->used += src_len; + c->data[c->used] = '\0'; + c->used++; + } + return ret; +} + +void +iks_stack_stat (ikstack *s, size_t *allocated, size_t *used) +{ + ikschunk *c; + + if (allocated) { + *allocated = s->allocated; + } + if (used) { + *used = 0; + for (c = s->meta; c; c = c->next) { + (*used) += c->used; + } + for (c = s->data; c; c = c->next) { + (*used) += c->used; + } + } +} + +void +iks_stack_delete (ikstack *s) +{ + ikschunk *c, *tmp; + + c = s->meta->next; + while (c) { + tmp = c->next; + iks_free (c); + c = tmp; + } + c = s->data->next; + while (c) { + tmp = c->next; + iks_free (c); + c = tmp; + } + iks_free (s); +} +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2004 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +enum cons_e { + C_CDATA = 0, + C_TAG_START, + C_TAG, + C_TAG_END, + C_ATTRIBUTE, + C_ATTRIBUTE_1, + C_ATTRIBUTE_2, + C_VALUE, + C_VALUE_APOS, + C_VALUE_QUOT, + C_WHITESPACE, + C_ENTITY, + C_COMMENT, + C_COMMENT_1, + C_COMMENT_2, + C_COMMENT_3, + C_MARKUP, + C_MARKUP_1, + C_SECT, + C_SECT_CDATA, + C_SECT_CDATA_1, + C_SECT_CDATA_2, + C_SECT_CDATA_3, + C_SECT_CDATA_4, + C_SECT_CDATA_C, + C_SECT_CDATA_E, + C_SECT_CDATA_E2, + C_PI +}; + +/* if you add a variable here, dont forget changing iks_parser_reset */ +struct iksparser_struct { + ikstack *s; + void *user_data; + iksTagHook *tagHook; + iksCDataHook *cdataHook; + iksDeleteHook *deleteHook; + /* parser context */ + char *stack; + size_t stack_pos; + size_t stack_max; + + enum cons_e context; + enum cons_e oldcontext; + + char *tag_name; + enum ikstagtype tagtype; + + unsigned int attmax; + unsigned int attcur; + int attflag; + char **atts; + int valflag; + + unsigned int entpos; + char entity[8]; + + unsigned long nr_bytes; + unsigned long nr_lines; + + int uni_max; + int uni_len; +}; + +iksparser * +iks_sax_new (void *user_data, iksTagHook *tagHook, iksCDataHook *cdataHook) +{ + iksparser *prs; + + prs = iks_malloc (sizeof (iksparser)); + if (NULL == prs) return NULL; + memset (prs, 0, sizeof (iksparser)); + prs->user_data = user_data; + prs->tagHook = tagHook; + prs->cdataHook = cdataHook; + return prs; +} + +iksparser * +iks_sax_extend (ikstack *s, void *user_data, iksTagHook *tagHook, iksCDataHook *cdataHook, iksDeleteHook *deleteHook) +{ + iksparser *prs; + + prs = iks_stack_alloc (s, sizeof (iksparser)); + if (NULL == prs) return NULL; + memset (prs, 0, sizeof (iksparser)); + prs->s = s; + prs->user_data = user_data; + prs->tagHook = tagHook; + prs->cdataHook = cdataHook; + prs->deleteHook = deleteHook; + return prs; +} + +ikstack * +iks_parser_stack (iksparser *prs) +{ + return prs->s; +} + +void * +iks_user_data (iksparser *prs) +{ + return prs->user_data; +} + +unsigned long +iks_nr_bytes (iksparser *prs) +{ + return prs->nr_bytes; +} + +unsigned long +iks_nr_lines (iksparser *prs) +{ + return prs->nr_lines; +} + +#define IS_WHITESPACE(x) ' ' == (x) || '\t' == (x) || '\r' == (x) || '\n' == (x) +#define NOT_WHITESPACE(x) ' ' != (x) && '\t' != (x) && '\r' != (x) && '\n' != (x) + +static int +stack_init (iksparser *prs) +{ + prs->stack = iks_malloc (128); + if (!prs->stack) return 0; + prs->stack_max = 128; + prs->stack_pos = 0; + return 1; +} + +static int +stack_expand (iksparser *prs, int len) +{ + size_t need; + off_t diff; + char *tmp; + need = len - (prs->stack_max - prs->stack_pos); + if (need < prs->stack_max) { + need = prs->stack_max * 2; + } else { + need = prs->stack_max + (need * 1.2); + } + tmp = iks_malloc (need); + if (!tmp) return 0; + diff = tmp - prs->stack; + memcpy (tmp, prs->stack, prs->stack_max); + iks_free (prs->stack); + prs->stack = tmp; + prs->stack_max = need; + prs->tag_name += diff; + if (prs->attflag != 0) { + unsigned int i = 0; + while (i < (prs->attmax * 2)) { + if (prs->atts[i]) prs->atts[i] += diff; + i++; + } + } + return 1; +} + +#define STACK_INIT \ + if (NULL == prs->stack && 0 == stack_init (prs)) return IKS_NOMEM + +#define STACK_PUSH_START (prs->stack + prs->stack_pos) + +#define STACK_PUSH(buf,len) \ +{ \ + char *sbuf = (buf); \ + size_t slen = (len); \ + if (prs->stack_max - prs->stack_pos <= slen) { \ + if (0 == stack_expand (prs, slen)) return IKS_NOMEM; \ + } \ + memcpy (prs->stack + prs->stack_pos, sbuf, slen); \ + prs->stack_pos += slen; \ +} + +#define STACK_PUSH_END \ +{ \ + if (prs->stack_pos >= prs->stack_max) { \ + if (0 == stack_expand (prs, 1)) return IKS_NOMEM; \ + } \ + prs->stack[prs->stack_pos] = '\0'; \ + prs->stack_pos++; \ +} + +static enum ikserror +sax_core (iksparser *prs, char *buf, int len) +{ + enum ikserror err; + int pos = 0, old = 0, re, stack_old = -1; + unsigned char c; + + while (pos < len) { + re = 0; + c = buf[pos]; + if (0 == c || 0xFE == c || 0xFF == c) return IKS_BADXML; + if (prs->uni_max) { + if ((c & 0xC0) != 0x80) return IKS_BADXML; + prs->uni_len++; + if (prs->uni_len == prs->uni_max) prs->uni_max = 0; + goto cont; + } else { + if (c & 0x80) { + unsigned char mask; + if ((c & 0x60) == 0x40) { + prs->uni_max = 2; + mask = 0x1F; + } else if ((c & 0x70) == 0x60) { + prs->uni_max = 3; + mask = 0x0F; + } else if ((c & 0x78) == 0x70) { + prs->uni_max = 4; + mask = 0x07; + } else if ((c & 0x7C) == 0x78) { + prs->uni_max = 5; + mask = 0x03; + } else if ((c & 0x7E) == 0x7C) { + prs->uni_max = 6; + mask = 0x01; + } else { + return IKS_BADXML; + } + if ((c & mask) == 0) return IKS_BADXML; + prs->uni_len = 1; + if (stack_old == -1 + && (prs->context == C_TAG + || prs->context == C_ATTRIBUTE_1 + || prs->context == C_VALUE_APOS + || prs->context == C_VALUE_QUOT)) stack_old = pos; + goto cont; + } + } + + switch (prs->context) { + case C_CDATA: + if ('&' == c) { + if (old < pos && prs->cdataHook) { + err = prs->cdataHook (prs->user_data, &buf[old], pos - old); + if (IKS_OK != err) return err; + } + prs->context = C_ENTITY; + prs->entpos = 0; + break; + } + if ('<' == c) { + if (old < pos && prs->cdataHook) { + err = prs->cdataHook (prs->user_data, &buf[old], pos - old); + if (IKS_OK != err) return err; + } + STACK_INIT; + prs->tag_name = STACK_PUSH_START; + if (!prs->tag_name) return IKS_NOMEM; + prs->context = C_TAG_START; + } + break; + + case C_TAG_START: + prs->context = C_TAG; + if ('/' == c) { + prs->tagtype = IKS_CLOSE; + break; + } + if ('?' == c) { + prs->context = C_PI; + break; + } + if ('!' == c) { + prs->context = C_MARKUP; + break; + } + prs->tagtype = IKS_OPEN; + stack_old = pos; + break; + + case C_TAG: + if (IS_WHITESPACE(c)) { + if (IKS_CLOSE == prs->tagtype) + prs->oldcontext = C_TAG_END; + else + prs->oldcontext = C_ATTRIBUTE; + prs->context = C_WHITESPACE; + if (stack_old != -1) STACK_PUSH (buf + stack_old, pos - stack_old); + stack_old = -1; + STACK_PUSH_END; + break; + } + if ('/' == c) { + if (IKS_CLOSE == prs->tagtype) return IKS_BADXML; + prs->tagtype = IKS_SINGLE; + prs->context = C_TAG_END; + if (stack_old != -1) STACK_PUSH (buf + stack_old, pos - stack_old); + stack_old = -1; + STACK_PUSH_END; + break; + } + if ('>' == c) { + prs->context = C_TAG_END; + if (stack_old != -1) STACK_PUSH (buf + stack_old, pos - stack_old); + stack_old = -1; + STACK_PUSH_END; + re = 1; + break; + } + if (stack_old == -1) stack_old = pos; + break; + + case C_TAG_END: + if (c != '>') return IKS_BADXML; + if (prs->tagHook) { + char **tmp; + if (prs->attcur == 0) tmp = NULL; else tmp = prs->atts; + err = prs->tagHook (prs->user_data, prs->tag_name, tmp, prs->tagtype); + if (IKS_OK != err) return err; + } + prs->stack_pos = 0; + stack_old = -1; + prs->attcur = 0; + prs->attflag = 0; + prs->context = C_CDATA; + old = pos + 1; + break; + + case C_ATTRIBUTE: + if ('/' == c) { + prs->tagtype = IKS_SINGLE; + prs->context = C_TAG_END; + break; + } + if ('>' == c) { + prs->context = C_TAG_END; + re = 1; + break; + } + if (!prs->atts) { + prs->attmax = 12; + prs->atts = iks_malloc (sizeof(char *) * 2 * 12); + if (!prs->atts) return IKS_NOMEM; + memset (prs->atts, 0, sizeof(char *) * 2 * 12); + prs->attcur = 0; + } else { + if (prs->attcur >= (prs->attmax * 2)) { + void *tmp; + prs->attmax += 12; + tmp = iks_malloc (sizeof(char *) * 2 * prs->attmax); + if (!tmp) return IKS_NOMEM; + memset (tmp, 0, sizeof(char *) * 2 * prs->attmax); + memcpy (tmp, prs->atts, sizeof(char *) * prs->attcur); + free (prs->atts); + prs->atts = tmp; + } + } + prs->attflag = 1; + prs->atts[prs->attcur] = STACK_PUSH_START; + stack_old = pos; + prs->context = C_ATTRIBUTE_1; + break; + + case C_ATTRIBUTE_1: + if ('=' == c) { + if (stack_old != -1) STACK_PUSH (buf + stack_old, pos - stack_old); + stack_old = -1; + STACK_PUSH_END; + prs->context = C_VALUE; + break; + } + if (stack_old == -1) stack_old = pos; + break; + + case C_ATTRIBUTE_2: + if ('/' == c) { + prs->tagtype = IKS_SINGLE; + prs->atts[prs->attcur] = NULL; + prs->context = C_TAG_END; + break; + } + if ('>' == c) { + prs->atts[prs->attcur] = NULL; + prs->context = C_TAG_END; + re = 1; + break; + } + prs->context = C_ATTRIBUTE; + re = 1; + break; + + case C_VALUE: + prs->atts[prs->attcur + 1] = STACK_PUSH_START; + if ('\'' == c) { + prs->context = C_VALUE_APOS; + break; + } + if ('"' == c) { + prs->context = C_VALUE_QUOT; + break; + } + return IKS_BADXML; + + case C_VALUE_APOS: + if ('\'' == c) { + if (stack_old != -1) STACK_PUSH (buf + stack_old, pos - stack_old); + stack_old = -1; + STACK_PUSH_END; + prs->oldcontext = C_ATTRIBUTE_2; + prs->context = C_WHITESPACE; + prs->attcur += 2; + } + if (stack_old == -1) stack_old = pos; + break; + + case C_VALUE_QUOT: + if ('"' == c) { + if (stack_old != -1) STACK_PUSH (buf + stack_old, pos - stack_old); + stack_old = -1; + STACK_PUSH_END; + prs->oldcontext = C_ATTRIBUTE_2; + prs->context = C_WHITESPACE; + prs->attcur += 2; + } + if (stack_old == -1) stack_old = pos; + break; + + case C_WHITESPACE: + if (NOT_WHITESPACE(c)) { + prs->context = prs->oldcontext; + re = 1; + } + break; + + case C_ENTITY: + if (';' == c) { + char hede[2]; + char t = '?'; + prs->entity[prs->entpos] = '\0'; + if (strcmp(prs->entity, "amp") == 0) + t = '&'; + else if (strcmp(prs->entity, "quot") == 0) + t = '"'; + else if (strcmp(prs->entity, "apos") == 0) + t = '\''; + else if (strcmp(prs->entity, "lt") == 0) + t = '<'; + else if (strcmp(prs->entity, "gt") == 0) + t = '>'; + old = pos + 1; + hede[0] = t; + if (prs->cdataHook) { + err = prs->cdataHook (prs->user_data, &hede[0], 1); + if (IKS_OK != err) return err; + } + prs->context = C_CDATA; + } else { + prs->entity[prs->entpos++] = buf[pos]; + if (prs->entpos > 7) return IKS_BADXML; + } + break; + + case C_COMMENT: + if ('-' != c) return IKS_BADXML; + prs->context = C_COMMENT_1; + break; + + case C_COMMENT_1: + if ('-' == c) prs->context = C_COMMENT_2; + break; + + case C_COMMENT_2: + if ('-' == c) + prs->context = C_COMMENT_3; + else + prs->context = C_COMMENT_1; + break; + + case C_COMMENT_3: + if ('>' != c) return IKS_BADXML; + prs->context = C_CDATA; + old = pos + 1; + break; + + case C_MARKUP: + if ('[' == c) { + prs->context = C_SECT; + break; + } + if ('-' == c) { + prs->context = C_COMMENT; + break; + } + prs->context = C_MARKUP_1; + + case C_MARKUP_1: + if ('>' == c) { + old = pos + 1; + prs->context = C_CDATA; + } + break; + + case C_SECT: + if ('C' == c) { + prs->context = C_SECT_CDATA; + break; + } + return IKS_BADXML; + + case C_SECT_CDATA: + if ('D' != c) return IKS_BADXML; + prs->context = C_SECT_CDATA_1; + break; + + case C_SECT_CDATA_1: + if ('A' != c) return IKS_BADXML; + prs->context = C_SECT_CDATA_2; + break; + + case C_SECT_CDATA_2: + if ('T' != c) return IKS_BADXML; + prs->context = C_SECT_CDATA_3; + break; + + case C_SECT_CDATA_3: + if ('A' != c) return IKS_BADXML; + prs->context = C_SECT_CDATA_4; + break; + + case C_SECT_CDATA_4: + if ('[' != c) return IKS_BADXML; + old = pos + 1; + prs->context = C_SECT_CDATA_C; + break; + + case C_SECT_CDATA_C: + if (']' == c) { + prs->context = C_SECT_CDATA_E; + if (prs->cdataHook && old < pos) { + err = prs->cdataHook (prs->user_data, &buf[old], pos - old); + if (IKS_OK != err) return err; + } + } + break; + + case C_SECT_CDATA_E: + if (']' == c) { + prs->context = C_SECT_CDATA_E2; + } else { + if (prs->cdataHook) { + err = prs->cdataHook (prs->user_data, "]", 1); + if (IKS_OK != err) return err; + } + old = pos; + prs->context = C_SECT_CDATA_C; + } + break; + + case C_SECT_CDATA_E2: + if ('>' == c) { + old = pos + 1; + prs->context = C_CDATA; + } else { + if (prs->cdataHook) { + err = prs->cdataHook (prs->user_data, "]]", 2); + if (IKS_OK != err) return err; + } + old = pos; + prs->context = C_SECT_CDATA_C; + } + break; + + case C_PI: + old = pos + 1; + if ('>' == c) prs->context = C_CDATA; + break; + } +cont: + if (0 == re) { + pos++; + prs->nr_bytes++; + if ('\n' == c) prs->nr_lines++; + } + } + + if (stack_old != -1) + STACK_PUSH (buf + stack_old, pos - stack_old); + + err = IKS_OK; + if (prs->cdataHook && (prs->context == C_CDATA || prs->context == C_SECT_CDATA_C) && old < pos) + err = prs->cdataHook (prs->user_data, &buf[old], pos - old); + return err; +} + +int +iks_parse (iksparser *prs, const char *data, size_t len, int finish) +{ + if (!data) return IKS_OK; + if (len == 0) len = strlen (data); + return sax_core (prs, (char *) data, len); +} + +void +iks_parser_reset (iksparser *prs) +{ + if (prs->deleteHook) prs->deleteHook (prs->user_data); + prs->stack_pos = 0; + prs->context = 0; + prs->oldcontext = 0; + prs->tagtype = 0; + prs->attcur = 0; + prs->attflag = 0; + prs->valflag = 0; + prs->entpos = 0; + prs->nr_bytes = 0; + prs->nr_lines = 0; + prs->uni_max = 0; + prs->uni_len = 0; +} + +void +iks_parser_delete (iksparser *prs) +{ + if (prs->deleteHook) prs->deleteHook (prs->user_data); + if (prs->stack) iks_free (prs->stack); + if (prs->atts) iks_free (prs->atts); + if (prs->s) iks_stack_delete (prs->s); else iks_free (prs); +} +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2004 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +#define IKS_COMMON \ + struct iks_struct *next, *prev; \ + struct iks_struct *parent; \ + enum ikstype type; \ + ikstack *s + +struct iks_struct { + IKS_COMMON; +}; + +struct iks_tag { + IKS_COMMON; + struct iks_struct *children, *last_child; + struct iks_struct *attribs, *last_attrib; + char *name; +}; + +#define IKS_TAG_NAME(x) ((struct iks_tag *) (x) )->name +#define IKS_TAG_CHILDREN(x) ((struct iks_tag *) (x) )->children +#define IKS_TAG_LAST_CHILD(x) ((struct iks_tag *) (x) )->last_child +#define IKS_TAG_ATTRIBS(x) ((struct iks_tag *) (x) )->attribs +#define IKS_TAG_LAST_ATTRIB(x) ((struct iks_tag *) (x) )->last_attrib + +struct iks_cdata { + IKS_COMMON; + char *cdata; + size_t len; +}; + +#define IKS_CDATA_CDATA(x) ((struct iks_cdata *) (x) )->cdata +#define IKS_CDATA_LEN(x) ((struct iks_cdata *) (x) )->len + +struct iks_attrib { + IKS_COMMON; + char *name; + char *value; +}; + +#define IKS_ATTRIB_NAME(x) ((struct iks_attrib *) (x) )->name +#define IKS_ATTRIB_VALUE(x) ((struct iks_attrib *) (x) )->value + +/***** Node Creating & Deleting *****/ + +iks * +iks_new (const char *name) +{ + ikstack *s; + iks *x; + + s = iks_stack_new (sizeof (struct iks_tag) * 6, 256); + if (!s) return NULL; + x = iks_new_within (name, s); + if (!x) { + iks_stack_delete (s); + return NULL; + } + return x; +} + +iks * +iks_new_within (const char *name, ikstack *s) +{ + iks *x; + size_t len; + + if (name) len = sizeof (struct iks_tag); else len = sizeof (struct iks_cdata); + x = iks_stack_alloc (s, len); + if (!x) return NULL; + memset (x, 0, len); + x->s = s; + x->type = IKS_TAG; + if (name) { + IKS_TAG_NAME (x) = iks_stack_strdup (s, name, 0); + if (!IKS_TAG_NAME (x)) return NULL; + } + return x; +} + +iks * +iks_insert (iks *x, const char *name) +{ + iks *y; + + if (!x) return NULL; + + y = iks_new_within (name, x->s); + if (!y) return NULL; + y->parent = x; + if (!IKS_TAG_CHILDREN (x)) IKS_TAG_CHILDREN (x) = y; + if (IKS_TAG_LAST_CHILD (x)) { + IKS_TAG_LAST_CHILD (x)->next = y; + y->prev = IKS_TAG_LAST_CHILD (x); + } + IKS_TAG_LAST_CHILD (x) = y; + return y; +} + +iks * +iks_insert_sibling (iks *x, const char *name) +{ + iks *y; + + if (!x) return NULL; + y = iks_new_within(name, x->s); + if (!y) return NULL; + + if (x->next) { + x->next->prev = y; + } else { + IKS_TAG_LAST_CHILD(x->parent) = y; + } + y->next = x->next; + x->next = y; + y->parent = x->parent; + y->prev = x; + + return y; +} + +iks * +iks_prepend(iks *x, const char *name) +{ + iks *y; + + if (!x) return NULL; + y = iks_new_within(name, x->s); + if (!y) return NULL; + + if (x->prev) { + x->prev->next = y; + } else { + IKS_TAG_CHILDREN(x->parent) = y; + } + y->prev = x->prev; + x->prev = y; + y->parent = x->parent; + y->next = x; + + return y; +} + +iks * +iks_insert_cdata (iks *x, const char *data, size_t len) +{ + iks *y; + + if(!x || !data) return NULL; + if(len == 0) len = strlen (data); + + y = IKS_TAG_LAST_CHILD (x); + if (y && y->type == IKS_CDATA) { + IKS_CDATA_CDATA (y) = iks_stack_strcat (x->s, IKS_CDATA_CDATA (y), IKS_CDATA_LEN (y), data, len); + IKS_CDATA_LEN (y) += len; + } else { + y = iks_insert (x, NULL); + if (!y) return NULL; + y->type = IKS_CDATA; + IKS_CDATA_CDATA (y) = iks_stack_strdup (x->s, data, len); + if (!IKS_CDATA_CDATA (y)) return NULL; + IKS_CDATA_LEN (y) = len; + } + return y; +} + +iks * +iks_set_cdata(iks *x, const char *data, size_t len) +{ + iks *y; + + if (!x || !data) return NULL; + if (len == 0) len = strlen(data); + + while (1) { + y = iks_child(x); + if (!y) break; + iks_hide(y); + } + + y = iks_insert_cdata(x, data, len); + return y; +} + +iks * +iks_prepend_cdata(iks *x, const char *data, size_t len) +{ + iks *y; + + if (!x || !data) return NULL; + if (len == 0) len = strlen (data); + + y = iks_new_within(NULL, x->s); + if (!y) return NULL; + y->type = IKS_CDATA; + IKS_CDATA_CDATA(y) = iks_stack_strdup(x->s, data, len); + if (!IKS_CDATA_CDATA (y)) return NULL; + IKS_CDATA_LEN (y) = len; + + if (x->prev) { + x->prev->next = y; + } else { + IKS_TAG_CHILDREN(x->parent) = y; + } + y->prev = x->prev; + x->prev = y; + y->parent = x->parent; + y->next = x; + + return y; +} + +iks * +iks_append_cdata(iks *x, const char *data, size_t len) +{ + iks *y; + + if (!x || !data) return NULL; + if (len == 0) len = strlen (data); + + y = iks_new_within(NULL, x->s); + if (!y) return NULL; + y->type = IKS_CDATA; + IKS_CDATA_CDATA(y) = iks_stack_strdup(x->s, data, len); + if (!IKS_CDATA_CDATA (y)) return NULL; + IKS_CDATA_LEN (y) = len; + + if (x->next) { + x->next->prev = y; + } else { + IKS_TAG_LAST_CHILD(x->parent) = y; + } + y->next = x->next; + x->next = y; + y->parent = x->parent; + y->prev = x; + + return y; +} + +iks * +iks_insert_attrib (iks *x, const char *name, const char *value) +{ + iks *y; + size_t len; + + if (!x) return NULL; + + y = IKS_TAG_ATTRIBS (x); + while (y) { + if (strcmp (name, IKS_ATTRIB_NAME (y)) == 0) break; + y = y->next; + } + if (NULL == y) { + if (!value) return NULL; + y = iks_stack_alloc (x->s, sizeof (struct iks_attrib)); + if (!y) return NULL; + memset (y, 0, sizeof (struct iks_attrib)); + y->type = IKS_ATTRIBUTE; + IKS_ATTRIB_NAME (y) = iks_stack_strdup (x->s, name, 0); + y->parent = x; + if (!IKS_TAG_ATTRIBS (x)) IKS_TAG_ATTRIBS (x) = y; + if (IKS_TAG_LAST_ATTRIB (x)) { + IKS_TAG_LAST_ATTRIB (x)->next = y; + y->prev = IKS_TAG_LAST_ATTRIB (x); + } + IKS_TAG_LAST_ATTRIB (x) = y; + } + + if (value) { + len = strlen (value); + IKS_ATTRIB_VALUE (y) = iks_stack_strdup (x->s, value, len); + if (!IKS_ATTRIB_VALUE (y)) return NULL; + } else { + if (y->next) y->next->prev = y->prev; + if (y->prev) y->prev->next = y->next; + if (IKS_TAG_ATTRIBS (x) == y) IKS_TAG_ATTRIBS (x) = y->next; + if (IKS_TAG_LAST_ATTRIB (x) == y) IKS_TAG_LAST_ATTRIB (x) = y->prev; + } + + return y; +} + +iks * +iks_insert_node (iks *x, iks *y) +{ + y->parent = x; + if (!IKS_TAG_CHILDREN (x)) IKS_TAG_CHILDREN (x) = y; + if (IKS_TAG_LAST_CHILD (x)) { + IKS_TAG_LAST_CHILD (x)->next = y; + y->prev = IKS_TAG_LAST_CHILD (x); + } + IKS_TAG_LAST_CHILD (x) = y; + return y; +} + +void +iks_hide (iks *x) +{ + iks *y; + + if (!x) return; + + if (x->prev) x->prev->next = x->next; + if (x->next) x->next->prev = x->prev; + y = x->parent; + if (y) { + if (IKS_TAG_CHILDREN (y) == x) IKS_TAG_CHILDREN (y) = x->next; + if (IKS_TAG_LAST_CHILD (y) == x) IKS_TAG_LAST_CHILD (y) = x->prev; + } +} + +void +iks_delete (iks *x) +{ + if (x) iks_stack_delete (x->s); +} + +/***** Node Traversing *****/ + +iks * +iks_next (iks *x) +{ + if (x) return x->next; + return NULL; +} + +iks * +iks_next_tag (iks *x) +{ + if (x) { + while (1) { + x = x->next; + if (NULL == x) break; + if (IKS_TAG == x->type) return x; + } + } + return NULL; +} + +iks * +iks_prev (iks *x) +{ + if (x) return x->prev; + return NULL; +} + +iks * +iks_prev_tag (iks *x) +{ + if (x) { + while (1) { + x = x->prev; + if (NULL == x) break; + if (IKS_TAG == x->type) return x; + } + } + return NULL; +} + +iks * +iks_parent (iks *x) +{ + if (x) return x->parent; + return NULL; +} + +iks * +iks_root (iks *x) +{ + if (x) { + while (x->parent) + x = x->parent; + } + return x; +} + +iks * +iks_child (iks *x) +{ + if (x) return IKS_TAG_CHILDREN (x); + return NULL; +} + +iks * +iks_first_tag (iks *x) +{ + if (x) { + x = IKS_TAG_CHILDREN (x); + while (x) { + if (IKS_TAG == x->type) return x; + x = x->next; + } + } + return NULL; +} + +iks * +iks_attrib (iks *x) +{ + if (x) return IKS_TAG_ATTRIBS (x); + return NULL; +} + +iks * +iks_find (iks *x, const char *name) +{ + iks *y; + + if (!x) return NULL; + y = IKS_TAG_CHILDREN (x); + while (y) { + if (IKS_TAG == y->type && IKS_TAG_NAME (y) && strcmp (IKS_TAG_NAME (y), name) == 0) return y; + y = y->next; + } + return NULL; +} + +char * +iks_find_cdata (iks *x, const char *name) +{ + iks *y; + + y = iks_find (x, name); + if (!y) return NULL; + y = IKS_TAG_CHILDREN (y); + if (!y || IKS_CDATA != y->type) return NULL; + return IKS_CDATA_CDATA (y); +} + +char * +iks_find_attrib (iks *x, const char *name) +{ + iks *y; + + if (!x) return NULL; + + y = IKS_TAG_ATTRIBS (x); + while (y) { + if (IKS_ATTRIB_NAME (y) && strcmp (IKS_ATTRIB_NAME (y), name) == 0) + return IKS_ATTRIB_VALUE (y); + y = y->next; + } + return NULL; +} + +iks * +iks_find_with_attrib (iks *x, const char *tagname, const char *attrname, const char *value) +{ + iks *y; + + if (NULL == x) return NULL; + + if (tagname) { + for (y = IKS_TAG_CHILDREN (x); y; y = y->next) { + if (IKS_TAG == y->type + && strcmp (IKS_TAG_NAME (y), tagname) == 0 + && iks_strcmp (iks_find_attrib (y, attrname), value) == 0) { + return y; + } + } + } else { + for (y = IKS_TAG_CHILDREN (x); y; y = y->next) { + if (IKS_TAG == y->type + && iks_strcmp (iks_find_attrib (y, attrname), value) == 0) { + return y; + } + } + } + return NULL; +} + +/***** Node Information *****/ + +ikstack * +iks_stack (iks *x) +{ + if (x) return x->s; + return NULL; +} + +enum ikstype +iks_type (iks *x) +{ + if (x) return x->type; + return IKS_NONE; +} + +char * +iks_name (iks *x) +{ + if (x) { + if (IKS_TAG == x->type) + return IKS_TAG_NAME (x); + else + return IKS_ATTRIB_NAME (x); + } + return NULL; +} + +char * +iks_cdata (iks *x) +{ + if (x) { + if (IKS_CDATA == x->type) + return IKS_CDATA_CDATA (x); + else + return IKS_ATTRIB_VALUE (x); + } + return NULL; +} + +size_t +iks_cdata_size (iks *x) +{ + if (x) return IKS_CDATA_LEN (x); + return 0; +} + +int +iks_has_children (iks *x) +{ + if (x && IKS_TAG == x->type && IKS_TAG_CHILDREN (x)) return 1; + return 0; +} + +int +iks_has_attribs (iks *x) +{ + if (x && IKS_TAG == x->type && IKS_TAG_ATTRIBS (x)) return 1; + return 0; +} + +/***** Serializing *****/ + +static size_t +escape_size (char *src, size_t len) +{ + size_t i, sz; + char c; + + sz = 0; + for (i = 0; i < len; i++) { + c = src[i]; + switch (c) { + case '&': sz += 5; break; + case '\'': sz += 6; break; + case '"': sz += 6; break; + case '<': sz += 4; break; + case '>': sz += 4; break; + default: sz++; break; + } + } + return sz; +} + +static char * +my_strcat (char *dest, char *src, size_t len) +{ + if (0 == len) len = strlen (src); + memcpy (dest, src, len); + return dest + len; +} + +static char * +escape (char *dest, char *src, size_t len) +{ + char c; + size_t i; + int j = 0; + + for (i = 0; i < len; i++) { + c = src[i]; + if ('&' == c || '<' == c || '>' == c || '\'' == c || '"' == c) { + if (i - j > 0) dest = my_strcat (dest, src + j, i - j); + j = i + 1; + switch (c) { + case '&': dest = my_strcat (dest, "&", 5); break; + case '\'': dest = my_strcat (dest, "'", 6); break; + case '"': dest = my_strcat (dest, """, 6); break; + case '<': dest = my_strcat (dest, "<", 4); break; + case '>': dest = my_strcat (dest, ">", 4); break; + } + } + } + if (i - j > 0) dest = my_strcat (dest, src + j, i - j); + return dest; +} + +char * +iks_string (ikstack *s, iks *x) +{ + size_t size; + int level, dir; + iks *y, *z; + char *ret, *t; + + if (!x) return NULL; + + if (x->type == IKS_CDATA) { + if (s) { + return iks_stack_strdup (s, IKS_CDATA_CDATA (x), IKS_CDATA_LEN (x)); + } else { + ret = iks_malloc (IKS_CDATA_LEN (x)); + memcpy (ret, IKS_CDATA_CDATA (x), IKS_CDATA_LEN (x)); + return ret; + } + } + + size = 0; + level = 0; + dir = 0; + y = x; + while (1) { + if (dir==0) { + if (y->type == IKS_TAG) { + size++; + size += strlen (IKS_TAG_NAME (y)); + for (z = IKS_TAG_ATTRIBS (y); z; z = z->next) { + size += 4 + strlen (IKS_ATTRIB_NAME (z)) + + escape_size (IKS_ATTRIB_VALUE (z), strlen (IKS_ATTRIB_VALUE (z))); + } + if (IKS_TAG_CHILDREN (y)) { + size++; + y = IKS_TAG_CHILDREN (y); + level++; + continue; + } else { + size += 2; + } + } else { + size += escape_size (IKS_CDATA_CDATA (y), IKS_CDATA_LEN (y)); + } + } + z = y->next; + if (z) { + if (0 == level) { + if (IKS_TAG_CHILDREN (y)) size += 3 + strlen (IKS_TAG_NAME (y)); + break; + } + y = z; + dir = 0; + } else { + y = y->parent; + level--; + if (level >= 0) size += 3 + strlen (IKS_TAG_NAME (y)); + if (level < 1) break; + dir = 1; + } + } + + if (s) ret = iks_stack_alloc (s, size + 1); + else ret = iks_malloc (size + 1); + + if (!ret) return NULL; + + t = ret; + level = 0; + dir = 0; + while (1) { + if (dir==0) { + if (x->type == IKS_TAG) { + *t++ = '<'; + t = my_strcat (t, IKS_TAG_NAME (x), 0); + y = IKS_TAG_ATTRIBS (x); + while (y) { + *t++ = ' '; + t = my_strcat (t, IKS_ATTRIB_NAME (y), 0); + *t++ = '='; + *t++ = '"'; + t = escape (t, IKS_ATTRIB_VALUE (y), strlen (IKS_ATTRIB_VALUE (y))); + *t++ = '"'; + y = y->next; + } + if (IKS_TAG_CHILDREN (x)) { + *t++ = '>'; + x = IKS_TAG_CHILDREN (x); + level++; + continue; + } else { + *t++ = '/'; + *t++ = '>'; + } + } else { + t = escape (t, IKS_CDATA_CDATA (x), IKS_CDATA_LEN (x)); + } + } + y = x->next; + if (y) { + if (0 == level) { + if (IKS_TAG_CHILDREN (x)) { + *t++ = '<'; + *t++ = '/'; + t = my_strcat (t, IKS_TAG_NAME (x), 0); + *t++ = '>'; + } + break; + } + x = y; + dir = 0; + } else { + x = x->parent; + level--; + if (level >= 0) { + *t++ = '<'; + *t++ = '/'; + t = my_strcat (t, IKS_TAG_NAME (x), 0); + *t++ = '>'; + } + if (level < 1) break; + dir = 1; + } + } + *t = '\0'; + + return ret; +} + +/***** Copying *****/ + +iks * +iks_copy_within (iks *x, ikstack *s) +{ + int level=0, dir=0; + iks *copy = NULL; + iks *cur = NULL; + iks *y; + + while (1) { + if (dir == 0) { + if (x->type == IKS_TAG) { + if (copy == NULL) { + copy = iks_new_within (IKS_TAG_NAME (x), s); + cur = copy; + } else { + cur = iks_insert (cur, IKS_TAG_NAME (x)); + } + for (y = IKS_TAG_ATTRIBS (x); y; y = y->next) { + iks_insert_attrib (cur, IKS_ATTRIB_NAME (y), IKS_ATTRIB_VALUE (y)); + } + if (IKS_TAG_CHILDREN (x)) { + x = IKS_TAG_CHILDREN (x); + level++; + continue; + } else { + cur = cur->parent; + } + } else { + iks_insert_cdata (cur, IKS_CDATA_CDATA (x), IKS_CDATA_LEN (x)); + } + } + y = x->next; + if (y) { + if (0 == level) break; + x = y; + dir = 0; + } else { + if (level < 2) break; + level--; + x = x->parent; + cur = cur->parent; + dir = 1; + } + } + return copy; +} + +iks * +iks_copy (iks *x) +{ + return iks_copy_within (x, iks_stack_new (sizeof (struct iks_tag) * 6, 256)); +} +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2003 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +struct dom_data { + iks **iksptr; + iks *current; + size_t chunk_size; +}; + +static int +tagHook (struct dom_data *data, char *name, char **atts, int type) +{ + iks *x; + + if (IKS_OPEN == type || IKS_SINGLE == type) { + if (data->current) { + x = iks_insert (data->current, name); + } else { + ikstack *s; + s = iks_stack_new (data->chunk_size, data->chunk_size); + x = iks_new_within (name, s); + } + if (atts) { + int i=0; + while (atts[i]) { + iks_insert_attrib (x, atts[i], atts[i+1]); + i += 2; + } + } + data->current = x; + } + if (IKS_CLOSE == type || IKS_SINGLE == type) { + x = iks_parent (data->current); + if (iks_strcmp(IKS_TAG_NAME(data->current), name) != 0) + return IKS_BADXML; + if (x) + data->current = x; + else { + *(data->iksptr) = data->current; + data->current = NULL; + } + } + return IKS_OK; +} + +static int +cdataHook (struct dom_data *data, char *cdata, size_t len) +{ + if (data->current) iks_insert_cdata (data->current, cdata, len); + return IKS_OK; +} + +static void +deleteHook (struct dom_data *data) +{ + if (data->current) iks_delete (data->current); + data->current = NULL; +} + +iksparser * +iks_dom_new (iks **iksptr) +{ + ikstack *s; + struct dom_data *data; + + *iksptr = NULL; + s = iks_stack_new (DEFAULT_DOM_CHUNK_SIZE, 0); + if (!s) return NULL; + data = iks_stack_alloc (s, sizeof (struct dom_data)); + data->iksptr = iksptr; + data->current = NULL; + data->chunk_size = DEFAULT_DOM_IKS_CHUNK_SIZE; + return iks_sax_extend (s, data, (iksTagHook *) tagHook, (iksCDataHook *) cdataHook, (iksDeleteHook *) deleteHook); +} + +void +iks_set_size_hint (iksparser *prs, size_t approx_size) +{ + size_t cs; + struct dom_data *data = iks_user_data (prs); + + cs = approx_size / 10; + if (cs < DEFAULT_DOM_IKS_CHUNK_SIZE) cs = DEFAULT_DOM_IKS_CHUNK_SIZE; + data->chunk_size = cs; +} + +iks * +iks_tree (const char *xml_str, size_t len, int *err) +{ + iksparser *prs; + iks *x; + int e; + + if (0 == len) len = strlen (xml_str); + prs = iks_dom_new (&x); + if (!prs) { + if (err) *err = IKS_NOMEM; + return NULL; + } + e = iks_parse (prs, xml_str, len, 1); + if (err) *err = e; + iks_parser_delete (prs); + return x; +} + +int +iks_load (const char *fname, iks **xptr) +{ + iksparser *prs; + char *buf; + FILE *f; + int len, done = 0; + int ret; + + *xptr = NULL; + + buf = iks_malloc (FILE_IO_BUF_SIZE); + if (!buf) return IKS_NOMEM; + ret = IKS_NOMEM; + prs = iks_dom_new (xptr); + if (prs) { + f = fopen (fname, "r"); + if (f) { + while (0 == done) { + len = fread (buf, 1, FILE_IO_BUF_SIZE, f); + if (len < FILE_IO_BUF_SIZE) { + if (0 == feof (f)) { + ret = IKS_FILE_RWERR; + break; + } + if (len == 0) ret = IKS_OK; + done = 1; + } + if (len > 0) { + int e; + e = iks_parse (prs, buf, len, done); + if (IKS_OK != e) { + ret = e; + break; + } + if (done) ret = IKS_OK; + } + } + fclose (f); + } else { + if (ENOENT == errno) ret = IKS_FILE_NOFILE; + else ret = IKS_FILE_NOACCESS; + } + iks_parser_delete (prs); + } + iks_free (buf); + return ret; +} + +int +iks_save (const char *fname, iks *x) +{ + FILE *f; + char *data; + int ret; + + ret = IKS_NOMEM; + data = iks_string (NULL, x); + if (data) { + ret = IKS_FILE_NOACCESS; + f = fopen (fname, "w"); + if (f) { + ret = IKS_FILE_RWERR; + if (fputs (data, f) >= 0) ret = IKS_OK; + fclose (f); + } + iks_free (data); + } + return ret; +} diff --git a/src/iksemel.h b/src/iksemel.h new file mode 100644 index 0000000..a873e89 --- /dev/null +++ b/src/iksemel.h @@ -0,0 +1,407 @@ +/* iksemel (XML parser for Jabber) +** Copyright (C) 2000-2004 Gurer Ozen +** This code is free software; you can redistribute it and/or +** modify it under the terms of GNU Lesser General Public License. +*/ + +#ifndef IKSEMEL_H +#define IKSEMEL_H 1 + +#ifdef __cplusplus +#include /* size_t for C++ */ +extern "C" { +#else +#include /* size_t for C */ +#endif + +/***** object stack *****/ + +struct ikstack_struct; +typedef struct ikstack_struct ikstack; + +ikstack *iks_stack_new (size_t meta_chunk, size_t data_chunk); +void *iks_stack_alloc (ikstack *s, size_t size); +char *iks_stack_strdup (ikstack *s, const char *src, size_t len); +char *iks_stack_strcat (ikstack *s, char *old, size_t old_len, const char *src, size_t src_len); +void iks_stack_stat (ikstack *s, size_t *allocated, size_t *used); +void iks_stack_delete (ikstack *s); + +/***** utilities *****/ + +void *iks_malloc (size_t size); +void iks_free (void *ptr); +void iks_set_mem_funcs (void *(*malloc_func)(size_t size), void (*free_func)(void *ptr)); + +char *iks_strdup (const char *src); +char *iks_strcat (char *dest, const char *src); +int iks_strcmp (const char *a, const char *b); +int iks_strcasecmp (const char *a, const char *b); +int iks_strncmp (const char *a, const char *b, size_t n); +int iks_strncasecmp (const char *a, const char *b, size_t n); +size_t iks_strlen (const char *src); +char *iks_escape (ikstack *s, char *src, size_t len); +char *iks_unescape (ikstack *s, char *src, size_t len); + +/***** dom tree *****/ + +enum ikstype { + IKS_NONE = 0, + IKS_TAG, + IKS_ATTRIBUTE, + IKS_CDATA +}; + +struct iks_struct; +typedef struct iks_struct iks; + +iks *iks_new (const char *name); +iks *iks_new_within (const char *name, ikstack *s); +iks *iks_insert (iks *x, const char *name); +iks *iks_insert_sibling (iks *x, const char *name); +iks *iks_prepend(iks *x, const char *name); +iks *iks_insert_cdata (iks *x, const char *data, size_t len); +iks *iks_append_cdata(iks *x, const char *data, size_t len); +iks *iks_prepend_cdata(iks *x, const char *data, size_t len); +iks *iks_set_cdata(iks *x, const char *data, size_t len); +iks *iks_insert_attrib (iks *x, const char *name, const char *value); +iks *iks_insert_node (iks *x, iks *y); +void iks_hide (iks *x); +void iks_delete (iks *x); +iks *iks_next (iks *x); +iks *iks_next_tag (iks *x); +iks *iks_prev (iks *x); +iks *iks_prev_tag (iks *x); +iks *iks_parent (iks *x); +iks *iks_root (iks *x); +iks *iks_child (iks *x); +iks *iks_first_tag (iks *x); +iks *iks_attrib (iks *x); +iks *iks_find (iks *x, const char *name); +char *iks_find_cdata (iks *x, const char *name); +char *iks_find_attrib (iks *x, const char *name); +iks *iks_find_with_attrib (iks *x, const char *tagname, const char *attrname, const char *value); +ikstack *iks_stack (iks *x); +enum ikstype iks_type (iks *x); +char *iks_name (iks *x); +char *iks_cdata (iks *x); +size_t iks_cdata_size (iks *x); +int iks_has_children (iks *x); +int iks_has_attribs (iks *x); +char *iks_string (ikstack *s, iks *x); +iks *iks_copy (iks *x); +iks *iks_copy_within (iks *x, ikstack *s); + +/***** sax parser *****/ + +enum ikserror { + IKS_OK = 0, + IKS_NOMEM, + IKS_BADXML, + IKS_HOOK +}; + +enum ikstagtype { + IKS_OPEN, + IKS_CLOSE, + IKS_SINGLE +}; + +typedef int (iksTagHook)(void *user_data, char *name, char **atts, int type); +typedef int (iksCDataHook)(void *user_data, char *data, size_t len); +typedef void (iksDeleteHook)(void *user_data); + +struct iksparser_struct; +typedef struct iksparser_struct iksparser; + +iksparser *iks_sax_new (void *user_data, iksTagHook *tagHook, iksCDataHook *cdataHook); +iksparser *iks_sax_extend (ikstack *s, void *user_data, iksTagHook *tagHook, iksCDataHook *cdataHook, iksDeleteHook *deleteHook); +ikstack *iks_parser_stack (iksparser *prs); +void *iks_user_data (iksparser *prs); +unsigned long iks_nr_bytes (iksparser *prs); +unsigned long iks_nr_lines (iksparser *prs); +int iks_parse (iksparser *prs, const char *data, size_t len, int finish); +void iks_parser_reset (iksparser *prs); +void iks_parser_delete (iksparser *prs); + +/***** dom parser *****/ + +enum iksfileerror { + IKS_FILE_NOFILE = 4, + IKS_FILE_NOACCESS, + IKS_FILE_RWERR +}; + +iksparser *iks_dom_new (iks **iksptr); +void iks_set_size_hint (iksparser *prs, size_t approx_size); +iks *iks_tree (const char *xml_str, size_t len, int *err); +int iks_load (const char *fname, iks **xptr); +int iks_save (const char *fname, iks *x); + +/***** transport layer *****/ + +typedef void (iksTClose)(void *socket); +typedef int (iksTConnect)(iksparser *prs, void **socketptr, const char *server, int port); +typedef int (iksTSend)(void *socket, const char *data, size_t len); +typedef int (iksTRecv)(void *socket, char *buffer, size_t buf_len, int timeout); +typedef int (iksTConnectFD)(iksparser *prs, void **socketptr, void *fd); +typedef void *(iksTGetFD)(void *socket); + +enum iksasyncevents { + IKS_ASYNC_RESOLVED, + IKS_ASYNC_CONNECTED, + IKS_ASYNC_WRITE, + IKS_ASYNC_WRITTEN, + IKS_ASYNC_READ, + IKS_ASYNC_CLOSED, + IKS_ASYNC_ERROR +}; + +typedef int (iksAsyncNotify)(void *user_data, int event, void *event_data); +typedef int (iksTConnectAsync)(iksparser *prs, void **socketptr, const char *server, int port, void *notify_data, iksAsyncNotify *notify_func); + +typedef struct ikstransport_struct { + /* basic api, connect can be NULL if one of the other connect funcs are used */ + iksTConnect *connect; + iksTSend *send; + iksTRecv *recv; + iksTClose *close; + /* optional fd api */ + iksTConnectFD *connect_fd; + iksTGetFD *get_fd; + /* optional async api */ + iksTConnectAsync *connect_async; +} ikstransport; + +extern ikstransport iks_default_transport; + +/***** stream parser *****/ + +enum iksneterror { + IKS_NET_NODNS = 4, + IKS_NET_NOSOCK, + IKS_NET_NOCONN, + IKS_NET_RWERR, + IKS_NET_NOTSUPP, + IKS_NET_TLSFAIL +}; + +enum iksnodetype { + IKS_NODE_START, + IKS_NODE_NORMAL, + IKS_NODE_ERROR, + IKS_NODE_STOP +}; + +enum ikssasltype { + IKS_SASL_PLAIN, + IKS_SASL_DIGEST_MD5 +}; + +#define IKS_JABBER_PORT 5222 + +typedef int (iksStreamHook)(void *user_data, int type, iks *node); +typedef void (iksLogHook)(void *user_data, const char *data, size_t size, int is_incoming); + +iksparser *iks_stream_new (char *name_space, void *user_data, iksStreamHook *streamHook); +void *iks_stream_user_data (iksparser *prs); +void iks_set_log_hook (iksparser *prs, iksLogHook *logHook); +int iks_connect_tcp (iksparser *prs, const char *server, int port); +int iks_connect_fd (iksparser *prs, int fd); +int iks_connect_via (iksparser *prs, const char *server, int port, const char *server_name); +int iks_connect_with (iksparser *prs, const char *server, int port, const char *server_name, ikstransport *trans); +int iks_connect_async (iksparser *prs, const char *server, int port, void *notify_data, iksAsyncNotify *notify_func); +int iks_connect_async_with (iksparser *prs, const char *server, int port, const char *server_name, ikstransport *trans, void *notify_data, iksAsyncNotify *notify_func); +int iks_fd (iksparser *prs); +int iks_recv (iksparser *prs, int timeout); +int iks_send_header (iksparser *prs, const char *to); +int iks_send (iksparser *prs, iks *x); +int iks_send_raw (iksparser *prs, const char *xmlstr); +void iks_disconnect (iksparser *prs); +int iks_has_tls (void); +int iks_is_secure (iksparser *prs); +int iks_start_tls (iksparser *prs); +int iks_start_sasl (iksparser *prs, enum ikssasltype type, char *username, char *pass); + +/***** jabber *****/ + +#define IKS_NS_CLIENT "jabber:client" +#define IKS_NS_SERVER "jabber:server" +#define IKS_NS_AUTH "jabber:iq:auth" +#define IKS_NS_AUTH_0K "jabber:iq:auth:0k" +#define IKS_NS_REGISTER "jabber:iq:register" +#define IKS_NS_ROSTER "jabber:iq:roster" +#define IKS_NS_XROSTER "jabber:x:roster" +#define IKS_NS_OFFLINE "jabber:x:offline" +#define IKS_NS_AGENT "jabber:iq:agent" +#define IKS_NS_AGENTS "jabber:iq:agents" +#define IKS_NS_BROWSE "jabber:iq:browse" +#define IKS_NS_CONFERENCE "jabber:iq:conference" +#define IKS_NS_DELAY "jabber:x:delay" +#define IKS_NS_VERSION "jabber:iq:version" +#define IKS_NS_TIME "jabber:iq:time" +#define IKS_NS_VCARD "vcard-temp" +#define IKS_NS_PRIVATE "jabber:iq:private" +#define IKS_NS_SEARCH "jabber:iq:search" +#define IKS_NS_OOB "jabber:iq:oob" +#define IKS_NS_XOOB "jabber:x:oob" +#define IKS_NS_ADMIN "jabber:iq:admin" +#define IKS_NS_FILTER "jabber:iq:filter" +#define IKS_NS_GATEWAY "jabber:iq:gateway" +#define IKS_NS_LAST "jabber:iq:last" +#define IKS_NS_SIGNED "jabber:x:signed" +#define IKS_NS_ENCRYPTED "jabber:x:encrypted" +#define IKS_NS_ENVELOPE "jabber:x:envelope" +#define IKS_NS_EVENT "jabber:x:event" +#define IKS_NS_EXPIRE "jabber:x:expire" +#define IKS_NS_XHTML "http://www.w3.org/1999/xhtml" +#define IKS_NS_XMPP_SASL "urn:ietf:params:xml:ns:xmpp-sasl" +#define IKS_NS_XMPP_BIND "urn:ietf:params:xml:ns:xmpp-bind" +#define IKS_NS_XMPP_SESSION "urn:ietf:params:xml:ns:xmpp-session" + +#define IKS_ID_USER 1 +#define IKS_ID_SERVER 2 +#define IKS_ID_RESOURCE 4 +#define IKS_ID_PARTIAL IKS_ID_USER | IKS_ID_SERVER +#define IKS_ID_FULL IKS_ID_USER | IKS_ID_SERVER | IKS_ID_RESOURCE + +#define IKS_STREAM_STARTTLS 1 +#define IKS_STREAM_SESSION 2 +#define IKS_STREAM_BIND 4 +#define IKS_STREAM_SASL_PLAIN 8 +#define IKS_STREAM_SASL_MD5 16 + +typedef struct iksid_struct { + char *user; + char *server; + char *resource; + char *partial; + char *full; +} iksid; + +iksid *iks_id_new (ikstack *s, const char *jid); +int iks_id_cmp (iksid *a, iksid *b, int parts); + +enum ikspaktype { + IKS_PAK_NONE = 0, + IKS_PAK_MESSAGE, + IKS_PAK_PRESENCE, + IKS_PAK_IQ, + IKS_PAK_S10N +}; + +enum iksubtype { + IKS_TYPE_NONE = 0, + IKS_TYPE_ERROR, + + IKS_TYPE_CHAT, + IKS_TYPE_GROUPCHAT, + IKS_TYPE_HEADLINE, + + IKS_TYPE_GET, + IKS_TYPE_SET, + IKS_TYPE_RESULT, + + IKS_TYPE_SUBSCRIBE, + IKS_TYPE_SUBSCRIBED, + IKS_TYPE_UNSUBSCRIBE, + IKS_TYPE_UNSUBSCRIBED, + IKS_TYPE_PROBE, + IKS_TYPE_AVAILABLE, + IKS_TYPE_UNAVAILABLE +}; + +enum ikshowtype { + IKS_SHOW_UNAVAILABLE = 0, + IKS_SHOW_AVAILABLE, + IKS_SHOW_CHAT, + IKS_SHOW_AWAY, + IKS_SHOW_XA, + IKS_SHOW_DND +}; + +typedef struct ikspak_struct { + iks *x; + iksid *from; + iks *query; + char *ns; + char *id; + enum ikspaktype type; + enum iksubtype subtype; + enum ikshowtype show; +} ikspak; + +ikspak *iks_packet (iks *x); + +iks *iks_make_auth (iksid *id, const char *pass, const char *sid); +iks *iks_make_msg (enum iksubtype type, const char *to, const char *body); +iks *iks_make_s10n (enum iksubtype type, const char *to, const char *msg); +iks *iks_make_pres (enum ikshowtype show, const char *status); +iks *iks_make_iq (enum iksubtype type, const char *xmlns); +iks *iks_make_resource_bind(iksid *id); +iks *iks_make_session(void); +int iks_stream_features(iks *x); + +/***** jabber packet filter *****/ + +#define IKS_RULE_DONE 0 +#define IKS_RULE_ID 1 +#define IKS_RULE_TYPE 2 +#define IKS_RULE_SUBTYPE 4 +#define IKS_RULE_FROM 8 +#define IKS_RULE_FROM_PARTIAL 16 +#define IKS_RULE_NS 32 + +enum iksfilterret { + IKS_FILTER_PASS, + IKS_FILTER_EAT +}; + +typedef int (iksFilterHook)(void *user_data, ikspak *pak); + +struct iksfilter_struct; +typedef struct iksfilter_struct iksfilter; +struct iksrule_struct; +typedef struct iksrule_struct iksrule; + +iksfilter *iks_filter_new (void); +iksrule *iks_filter_add_rule (iksfilter *f, iksFilterHook *filterHook, void *user_data, ...); +void iks_filter_remove_rule (iksfilter *f, iksrule *rule); +void iks_filter_remove_hook (iksfilter *f, iksFilterHook *filterHook); +void iks_filter_packet (iksfilter *f, ikspak *pak); +void iks_filter_delete (iksfilter *f); + +/***** sha1 *****/ + +struct iksha_struct; +typedef struct iksha_struct iksha; + +iksha *iks_sha_new (void); +void iks_sha_reset (iksha *sha); +void iks_sha_hash (iksha *sha, const unsigned char *data, size_t len, int finish); +void iks_sha_print (iksha *sha, char *hash); +void iks_sha_delete (iksha *sha); +void iks_sha (const char *data, char *hash); + +/***** md5 *****/ + +struct ikmd5_struct; +typedef struct iksmd5_struct iksmd5; + +iksmd5 *iks_md5_new(void); +void iks_md5_reset(iksmd5 *md5); +void iks_md5_hash(iksmd5 *md5, const unsigned char *data, size_t slen, int finish); +void iks_md5_delete(iksmd5 *md5); +void iks_md5_print(iksmd5 *md5, char *buf); +void iks_md5_digest(iksmd5 *md5, unsigned char *digest); +void iks_md5(const char *data, char *buf); + +/***** base64 *****/ + +char *iks_base64_decode(const char *buf); +char *iks_base64_encode(const char *buf, int len); + +#ifdef __cplusplus +} +#endif + +#endif /* IKSEMEL_H */ diff --git a/src/pyiks.c b/src/pyiks.c new file mode 100644 index 0000000..5fab659 --- /dev/null +++ b/src/pyiks.c @@ -0,0 +1,1039 @@ +/* +** Copyright (c) 2005-2008 TUBITAK/UEKAE +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 2 of the License, or (at your +** option) any later version. Please read the COPYING file. +*/ + +#include +#include "iksemel.h" + +PyObject *piksemel_module; + + +/*** Exceptions ***/ + +static PyObject *ParseError; +static PyObject *NotTag; +static PyObject *NotData; + +/*** Types ***/ + +typedef struct { + PyObject_HEAD + iks *document; +} Document; + +typedef struct { + PyObject_HEAD + Document *doc; + iks *node; +} Node; + +typedef struct { + PyObject_HEAD + Document *doc; + iks *node; + int tags; + char *tagname; +} Iter; + +static void Document_dealloc(Document *self); + +static PyTypeObject Document_type = { + PyVarObject_HEAD_INIT(NULL, 0) + "piksemel.Document", /* tp_name */ + sizeof(Document), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)Document_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "XML doc object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0 /* tp_new */ +}; + +static PyObject *Iter_iter(Iter *self); +static PyObject *Iter_next(Iter *self); + +static PyTypeObject Iter_type = { + PyVarObject_HEAD_INIT(NULL, 0) + "piksemel.Iter", /* tp_name */ + sizeof(Iter), /* tp"_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "XML iter object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)Iter_iter, /* tp_iter */ + (iternextfunc)Iter_next,/* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0 /* tp_new */ +}; + +static void Node_dealloc(Node *self); +static PyObject *Node_iter(Node *self); +static PyObject *Node_type_func(Node *self); +static PyObject *Node_reduce(Node *self, PyObject *args); +static PyObject *Node_data(Node *self); +static PyObject *Node_name(Node *self); +static PyObject *Node_attributes(Node *self, PyObject *args); +static PyObject *Node_getAttribute(Node *self, PyObject *args); +static PyObject *Node_setAttribute(Node *self, PyObject *args); +static PyObject *Node_getTag(Node *self, PyObject *args); +static PyObject *Node_getTagData(Node *self, PyObject *args); +static PyObject *Node_tags(Node *self, PyObject *args); +static PyObject *Node_firstChild(Node *self); +static PyObject *Node_parent(Node *self); +static PyObject *Node_root(Node *self); +static PyObject *Node_next(Node *self); +static PyObject *Node_nextTag(Node *self, PyObject *args); +static PyObject *Node_previous(Node *self); +static PyObject *Node_previousTag(Node *self, PyObject *args); +static PyObject *Node_toString(Node *self, PyObject *args); +static PyObject *Node_toPrettyString(Node *self, PyObject *args); +static PyObject *Node_insertTag(Node *self, PyObject *args); +static PyObject *Node_appendSibling(Node *self, PyObject *args); +static PyObject *Node_prependTag(Node *self, PyObject *args); +static PyObject *Node_insertData(Node *self, PyObject *args); +static PyObject *Node_prependData(Node *self, PyObject *args); +static PyObject *Node_appendSiblingData(Node *self, PyObject *args); +static PyObject *Node_insertNode(Node *self, PyObject *args); +static PyObject *Node_setData(Node *self, PyObject *args); +static PyObject *Node_hide(Node *self, PyObject *args); + +static PyMethodDef Node_methods[] = { + { "type", (PyCFunction)Node_type_func, METH_NOARGS, + "Return the type of node." }, + { "__reduce__", (PyCFunction)Node_reduce, METH_NOARGS, + "used by pickle" }, + { "name", (PyCFunction)Node_name, METH_NOARGS, + "Return tag name." }, + { "data", (PyCFunction)Node_data, METH_NOARGS, + "Return node's character data." }, + { "setData", (PyCFunction)Node_setData, METH_VARARGS, + "Set character data child of the tag." }, + { "attributes", (PyCFunction)Node_attributes, METH_NOARGS, + "Return node's attribute names." }, + { "getAttribute", (PyCFunction)Node_getAttribute, METH_VARARGS, + "Return value of a tag attribute." }, + { "setAttribute", (PyCFunction)Node_setAttribute, METH_VARARGS, + "Set the value of a tag attribute." }, + { "getTag", (PyCFunction)Node_getTag, METH_VARARGS, + "Return first child tag with the given name." }, + { "getTagData", (PyCFunction)Node_getTagData, METH_VARARGS, + "Return character data of the child tag with given name." }, + { "tags", (PyCFunction)Node_tags, METH_VARARGS, + "Iterate over all or optionally only matching tags." }, + { "firstChild", (PyCFunction)Node_firstChild, METH_NOARGS, + "Return first child node." }, + { "parent", (PyCFunction)Node_parent, METH_NOARGS, + "Return parent node." }, + { "root", (PyCFunction)Node_root, METH_NOARGS, + "Return topmost parent node." }, + { "nextTag", (PyCFunction)Node_nextTag, METH_VARARGS, + "Return next sibling tag node." }, + { "next", (PyCFunction)Node_next, METH_NOARGS, + "Return next sibling node." }, + { "previousTag", (PyCFunction)Node_previousTag, METH_VARARGS, + "Return previous sibling tag node." }, + { "previous", (PyCFunction)Node_previous, METH_NOARGS, + "Return previous sibling node." }, + { "toString", (PyCFunction)Node_toString, METH_NOARGS, + "Convert a document tree to XML string representation." }, + { "toPrettyString", (PyCFunction)Node_toPrettyString, METH_NOARGS, + "Convert a document tree to indented XML string representation." }, + { "insertTag", (PyCFunction)Node_insertTag, METH_VARARGS, + "Insert a child tag node with given name." }, + { "appendTag", (PyCFunction)Node_appendSibling, METH_VARARGS, + "Append a sibling tag node with given name." }, + { "prependTag", (PyCFunction)Node_prependTag, METH_VARARGS, + "Prepend a sibling tag node with given name." }, + { "insertData", (PyCFunction)Node_insertData, METH_VARARGS, + "Insert a child character data node with given text." }, + { "appendData", (PyCFunction)Node_appendSiblingData, METH_VARARGS, + "Append a sibling character data node with given text." }, + { "prependData", (PyCFunction)Node_prependData, METH_VARARGS, + "Prepend a sibling character data node with given text." }, + { "insertNode", (PyCFunction)Node_insertNode, METH_VARARGS, + "Insert another document as a child." }, + { "hide", (PyCFunction)Node_hide, METH_VARARGS, + "Hide tag from document tree." }, + { NULL } +}; + +static PyTypeObject Node_type = { + PyVarObject_HEAD_INIT(NULL, 0) + "piksemel.Node", /* tp_name */ + sizeof(Node), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)Node_dealloc,/* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "XML node object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)Node_iter, /* tp_iter */ + 0, /* tp_iternext */ + Node_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0 /* tp_new */ +}; + +static void +Document_dealloc(Document *self) +{ + if (self->document) iks_delete(self->document); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject * +new_node(Document *doc, iks *xml) +{ + Node *node; + int ref = 1; + + if (!xml) return PyErr_NoMemory(); + + if (!doc) { + doc = PyObject_New(Document, &Document_type); + doc->document = xml; + ref = 0; + } + node = PyObject_New(Node, &Node_type); + node->doc = doc; + if (ref) { + Py_INCREF(doc); + } + node->node = xml; + return (PyObject *)node; +} + +static PyObject * +Iter_iter(Iter *self) +{ + Py_INCREF(self); + return (PyObject *)self; +} + +static PyObject * +Iter_next(Iter *self) +{ + iks *node; + + node = self->node; + if (!node) return NULL; + + if (self->tags) { + self->node = iks_next_tag(node); + if (self->node && self->tagname) { + while (self->node && (strcmp(self->tagname, iks_name(self->node)) != 0)) { + self->node = iks_next_tag(self->node); + } + } + } else { + self->node = iks_next(node); + } + + return new_node(self->doc, node); +} + +static PyObject * +Node_iter(Node *self) +{ + Iter *iter; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + iter = PyObject_New(Iter, &Iter_type); + iter->node = iks_child(self->node); + iter->tags = 0; + iter->doc = self->doc; + iter->tagname = NULL; + return (PyObject *)iter; +} + +static void +Node_dealloc(Node *self) +{ + if (self->doc) { + Py_DECREF(self->doc); + } + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject * +Node_type_func(Node *self) +{ + PyObject *ret; + + ret = Py_BuildValue("i", iks_type(self->node)); + return ret; +} + +static PyObject * +Node_reduce(Node *self, PyObject *args) +{ + PyObject *ret; + PyObject *dict; + PyObject *tuple; + PyObject *func; + PyObject *state; + + state = Node_toString(self, args); + if (!state) return NULL; + + dict = PyModule_GetDict(piksemel_module); + if (!dict) return NULL; + func = PyDict_GetItemString(dict, "parseString"); + if (!func) return NULL; + + tuple = PyTuple_Pack(1, state); + if (!tuple) return NULL; + + ret = PyTuple_Pack(2, func, tuple); + + return ret; +} + +static PyObject * +Node_data(Node *self) +{ + PyObject *ret; + + if (iks_type(self->node) != IKS_CDATA) { + PyErr_SetNone(NotData); + return NULL; + } + + ret = Py_BuildValue("s", iks_cdata(self->node)); + return ret; +} + +static PyObject * +Node_name(Node *self) +{ + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + return Py_BuildValue("s", iks_name(self->node)); +} + +static PyObject * +Node_attributes(Node *self, PyObject *args) +{ + PyObject *ret, *p; + iks *attr; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + ret = PyList_New(0); + if (!ret) return NULL; + + for (attr = iks_attrib(self->node); attr; attr = iks_next(attr)) { + p = PyUnicode_FromString(iks_name(attr)); + if (p) PyList_Append(ret, p); + } + + return ret; +} + +static PyObject * +Node_getAttribute(Node *self, PyObject *args) +{ + PyObject *ret; + char *name; + char *val; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + val = iks_find_attrib(self->node, name); + if (!val) { + Py_INCREF(Py_None); + return Py_None; + } + + ret = Py_BuildValue("s", val); + return ret; +} + +static PyObject * +Node_setAttribute(Node *self, PyObject *args) +{ + char *name; + char *value; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + if (!PyArg_ParseTuple(args, "sz", &name, &value)) + return NULL; + + iks_insert_attrib(self->node, name, value); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +Node_setData(Node *self, PyObject *args) +{ + char *data; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s", &data)) + return NULL; + + iks_set_cdata(self->node, data, 0); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +Node_getTag(Node *self, PyObject *args) +{ + iks *child; + char *name; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + child = iks_find(self->node, name); + if (!child) { + Py_INCREF(Py_None); + return Py_None; + } + + return new_node(self->doc, child); +} + +static PyObject * +Node_getTagData(Node *self, PyObject *args) +{ + PyObject *ret; + char *name; + char *data; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + data = iks_find_cdata(self->node, name); + if (!data) { + Py_INCREF(Py_None); + return Py_None; + } + + ret = Py_BuildValue("s", data); + return ret; +} + +static PyObject * +Node_tags(Node *self, PyObject *args) +{ + Iter *iter; + char *name = NULL; + + if (!PyArg_ParseTuple(args, "|s", &name)) + return NULL; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + iter = PyObject_New(Iter, &Iter_type); + iter->doc = self->doc; + iter->node = iks_first_tag(self->node); + if (name && iter->node) { + while (iter->node && (strcmp(name, iks_name(iter->node)) != 0)) { + iter->node = iks_next_tag(iter->node); + } + } + iter->tags = 1; + iter->tagname = name; + return (PyObject *)iter; +} + +static PyObject * +Node_firstChild(Node *self) +{ + iks *child; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + child = iks_child(self->node); + if (!child) { + Py_INCREF(Py_None); + return Py_None; + } + + return new_node(self->doc, child); +} + +static PyObject * +Node_parent(Node *self) +{ + iks *parent; + + parent = iks_parent(self->node); + if (!parent) { + Py_INCREF(Py_None); + return Py_None; + } + + return new_node(self->doc, parent); +} + +static PyObject * +Node_root(Node *self) +{ + iks *root; + + root = iks_root(self->node); + if (!root) { + Py_INCREF(Py_None); + return Py_None; + } + + return new_node(self->doc, root); +} + +static PyObject * +Node_next(Node *self) +{ + iks *sibling; + + sibling = iks_next(self->node); + if (!sibling) { + Py_INCREF(Py_None); + return Py_None; + } + + return new_node(self->doc, sibling); +} + +static PyObject * +Node_nextTag(Node *self, PyObject *args) +{ + iks *sibling; + char *name = NULL; + + if (!PyArg_ParseTuple(args, "|s", &name)) + return NULL; + + sibling = iks_next_tag(self->node); + if (sibling && name) { + while (strcmp(iks_name(sibling), name) != 0) { + sibling = iks_next_tag(sibling); + if (!sibling) break; + } + } + if (!sibling) { + Py_INCREF(Py_None); + return Py_None; + } + + return new_node(self->doc, sibling); +} + +static PyObject * +Node_previous(Node *self) +{ + iks *sibling; + + sibling = iks_prev(self->node); + if (!sibling) { + Py_INCREF(Py_None); + return Py_None; + } + + return new_node(self->doc, sibling); +} + +static PyObject * +Node_previousTag(Node *self, PyObject *args) +{ + iks *sibling; + char *name = NULL; + + if (!PyArg_ParseTuple(args, "|s", &name)) + return NULL; + + sibling = iks_prev_tag(self->node); + if (sibling && name) { + while (strcmp(iks_name(sibling), name) != 0) { + sibling = iks_prev_tag(sibling); + if (!sibling) break; + } + } + if (!sibling) { + Py_INCREF(Py_None); + return Py_None; + } + + return new_node(self->doc, sibling); +} + +static PyObject * +Node_toString(Node *self, PyObject *args) +{ + PyObject *ret; + char *str; + + str = iks_string(NULL, self->node); + ret = Py_BuildValue("s", str); + iks_free(str); + + return ret; +} + +struct makeup_ctx { + unsigned int level; + int can_indent; +}; + +static void +prettify(struct makeup_ctx *ctx, iks *parent, iks *node) +{ + iks *x, *y, *a; + unsigned int i; + + for (x = iks_child(node); x; x = iks_next(x)) { + if (iks_type(x) == IKS_TAG) { + if (ctx->can_indent) { + iks_insert_cdata(parent, "\n", 1); + for (i = 0; i < ctx->level; ++i) { + iks_insert_cdata(parent, " ", 4); + } + } + y = iks_insert(parent, iks_name(x)); + for (a = iks_attrib(x); a; a = iks_next(a)) { + iks_insert_attrib(y, iks_name(a), iks_cdata(a)); + } + if (iks_child(x)) { + ctx->can_indent = 1; + ++ctx->level; + prettify(ctx, y, x); + --ctx->level; + if (!iks_next(x)) { + iks_insert_cdata(parent, "\n", 1); + for (i = 0; i < ctx->level - 1; ++i) { + iks_insert_cdata(parent, " ", 4); + } + } + } + ctx->can_indent = 1; + } else { + iks_insert_cdata(parent, iks_cdata(x), iks_cdata_size(x)); + ctx->can_indent = 0; + } + } +} + +static PyObject * +Node_toPrettyString(Node *self, PyObject *args) +{ + struct makeup_ctx ctx; + PyObject *ret; + iks *tree, *a; + char *str; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + ctx.level = 1; + ctx.can_indent = 1; + + tree = iks_new(iks_name(self->node)); + for (a = iks_attrib(self->node); a; a = iks_next(a)) { + iks_insert_attrib(tree, iks_name(a), iks_cdata(a)); + } + prettify(&ctx, tree, self->node); + + str = iks_string(NULL, tree); + ret = Py_BuildValue("s", str); + iks_free(str); + iks_delete(tree); + + return ret; +} + +static PyObject * +Node_insertTag(Node *self, PyObject *args) +{ + iks *node; + char *name; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + node = iks_insert(self->node, name); + + return new_node(self->doc, node); +} + +static PyObject * +Node_appendSibling(Node *self, PyObject *args) +{ + iks *node; + char *name; + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + if (iks_parent(self->node) == NULL) { + PyErr_SetString(NotTag, "Cannot append to the top level tag, use insert"); + return NULL; + } + + node = iks_insert_sibling(self->node, name); + + return new_node(self->doc, node); +} + +static PyObject * +Node_prependTag(Node *self, PyObject *args) +{ + iks *node; + char *name; + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + if (iks_parent(self->node) == NULL) { + PyErr_SetString(NotTag, "Cannot prepend to the top level tag, use insert"); + return NULL; + } + + node = iks_prepend(self->node, name); + + return new_node(self->doc, node); +} + +static PyObject * +Node_insertData(Node *self, PyObject *args) +{ + iks *node; + char *value; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s", &value)) + return NULL; + + node = iks_insert_cdata(self->node, value, 0); + + return new_node(self->doc, node); +} + +static PyObject * +Node_appendSiblingData(Node *self, PyObject *args) +{ + iks *node; + char *value; + + if (!PyArg_ParseTuple(args, "s", &value)) + return NULL; + + if (iks_parent(self->node) == NULL) { + PyErr_SetString(NotTag, "Cannot append to the top level tag, use insert"); + return NULL; + } + + node = iks_append_cdata(self->node, value, strlen(value)); + + return new_node(self->doc, node); +} + +static PyObject * +Node_prependData(Node *self, PyObject *args) +{ + iks *node; + char *value; + + if (!PyArg_ParseTuple(args, "s", &value)) + return NULL; + + if (iks_parent(self->node) == NULL) { + PyErr_SetString(NotTag, "Cannot prepend to the top level tag, use insert"); + return NULL; + } + + node = iks_prepend_cdata(self->node, value, strlen(value)); + + return new_node(self->doc, node); +} + +static PyObject * +Node_insertNode(Node *self, PyObject *args) +{ + iks *child; + Node *node; + + if (iks_type(self->node) != IKS_TAG) { + PyErr_SetNone(NotTag); + return NULL; + } + + if (!PyArg_ParseTuple(args, "O!", &Node_type, &node)) + return NULL; + + child = iks_copy_within(node->node, iks_stack(self->node)); + iks_insert_node(self->node, child); + + return new_node(self->doc, child); +} + +static PyObject * +Node_hide(Node *self, PyObject *args) +{ + iks_hide(self->node); + + Py_INCREF(Py_None); + return Py_None; +} + +/*** Module Functions ***/ + +static PyObject * +piks_parse(PyObject *self, PyObject *args) +{ + char *file; + iks *doc; + int e; + + if (!PyArg_ParseTuple(args, "s", &file)) + return NULL; + + e = iks_load(file, &doc); + switch (e) { + case IKS_OK: + break; + case IKS_NOMEM: + return PyErr_NoMemory(); + case IKS_BADXML: + PyErr_SetNone(ParseError); + return NULL; + default: + return PyErr_SetFromErrnoWithFilename(PyExc_OSError, file); + } + + return new_node(NULL, doc); +} + +static PyObject * +piks_parseString(PyObject *self, PyObject *args) +{ + iks *doc; + char *str; + int e; + + if (!PyArg_ParseTuple(args, "s", &str)) + return NULL; + + doc = iks_tree(str, 0, &e); + if (!doc) { + if (e == IKS_NOMEM) { + return PyErr_NoMemory(); + } else { + PyErr_SetNone(ParseError); + return NULL; + } + } + + return new_node(NULL, doc); +} + +static PyObject * +piks_newDocument(PyObject *self, PyObject *args) +{ + iks *doc; + char *name; + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + doc = iks_new(name); + + return new_node(NULL, doc); +} + +static PyMethodDef methods[] = { + { "parse", piks_parse, METH_VARARGS, + "Parse given XML file and generate document tree."}, + { "parseString", piks_parseString, METH_VARARGS, + "Parse given XML string and generate document tree."}, + { "newDocument", piks_newDocument, METH_VARARGS, + "Create a new document with given root tag name."}, + { NULL, NULL, 0, NULL } +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "piksemel", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + methods, /* m_methods */ +}; + +PyMODINIT_FUNC +PyInit_piksemel(void) +{ + PyObject *m; + + m = PyModule_Create(&moduledef); + /* constants */ + PyModule_AddIntConstant(m, "TAG", IKS_TAG); + PyModule_AddIntConstant(m, "ATTRIBUTE", IKS_ATTRIBUTE); + PyModule_AddIntConstant(m, "DATA", IKS_CDATA); + /* exceptions */ + ParseError = PyErr_NewException("piksemel.ParseError", NULL, NULL); + Py_INCREF(ParseError); + PyModule_AddObject(m, "ParseError", ParseError); + NotTag = PyErr_NewException("piksemel.NotTag", NULL, NULL); + Py_INCREF(NotTag); + PyModule_AddObject(m, "NotTag", NotTag); + NotData = PyErr_NewException("piksemel.NotData", NULL, NULL); + Py_INCREF(NotData); + PyModule_AddObject(m, "NotData", NotData); + /* types */ + Document_type.tp_new = PyType_GenericNew; + if (PyType_Ready(&Document_type) < 0) + return NULL; + Py_INCREF(&Document_type); + Iter_type.tp_new = PyType_GenericNew; + if (PyType_Ready(&Iter_type) < 0) + return NULL; + Py_INCREF(&Iter_type); + Node_type.tp_new = PyType_GenericNew; + if (PyType_Ready(&Node_type) < 0) + return NULL; + Py_INCREF(&Node_type); + PyModule_AddObject(m, "Node", (PyObject *)&Node_type); + + piksemel_module = m; + return piksemel_module; +} diff --git a/tests/attributes.py b/tests/attributes.py new file mode 100644 index 0000000..2e9066f --- /dev/null +++ b/tests/attributes.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import piksemel as iks + +doc_xml = '' + +doc = iks.newDocument("lala") +doc.setAttribute("a", "lala") +doc.setAttribute("b", "nanuk") +doc.setAttribute("b", "") +doc.setAttribute("a", None) +doc.setAttribute("c", "42") + +assert(doc.toString() == doc_xml) diff --git a/tests/generate.py b/tests/generate.py new file mode 100644 index 0000000..0213e1e --- /dev/null +++ b/tests/generate.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 + +import piksemel as iks + +doc_xml = """ + + + + blah & bleh + + + + +""".strip() + +doc = iks.newDocument("test") +doc.insertData("\n ") +a = doc.insertTag("a") +a.insertTag("b").insertTag("c") +doc.insertData("\n ") +item = doc.insertTag("item") +item.setAttribute("foo", "lala") +item.insertData("\n ") +item.insertTag("tuktuk").insertData("blah & bleh") +item.insertData("\n ") +item.insertTag("nanuk") +item.insertData("\n ") +doc.insertData("\n ") +a = doc.insertTag("a") +doc.insertData("\n") +assert(doc.toString() == doc_xml) + +# append test + +doc = iks.newDocument("lala") +a = doc.insertTag("nanik") +b = a.appendData("lala") +a = b.appendTag("lolo") +a = a.appendTag("iki") +b.appendData("hede") +doc.firstChild().appendData("fii") +assert(doc.toString() == "fiilalahede") + +# prepend test + +doc = iks.newDocument("lala") +a = doc.insertTag("foo") +a = a.prependTag("a") +a = a.prependTag("b") +a.prependData("hihi").prependData("oh").appendData("i") +a.appendTag("c") +assert(doc.toString() == "ohihihi") + +# insertNode test + +doc = iks.newDocument("a") +b = iks.newDocument("b") +b.setAttribute("lal", "foo") +b = doc.insertNode(b) +b.setAttribute("lal", "foofff") +assert(doc.toString() == '') + +# now with append, prepend and insertNode + +doc = iks.newDocument("test") + +a = iks.newDocument("a") +a.insertTag("b").insertTag("c") + +item = iks.newDocument("item") +node = item.insertData("\n ") +node.appendTag("tuktuk").insertData("blah & bleh") +node = item.insertData("\n ") +node.appendTag("nanuk").appendData("\n ") + +doc.insertNode(a).appendData("\n ") +doc.getTag("a").prependData("\n ") +del a +node = doc.insertNode(item) +node.setAttribute("foo", "lala") +del item +node = doc.insertData("\n ") +node.appendTag("a").appendData("\n") +assert(doc.toString() == doc_xml) + +# setData test +doc = iks.newDocument("a") +x = doc.insertTag("b") +x.insertTag("lala") +y = x.insertTag("bibi") +y.prependData("hodo") +assert(x.toString() == "hodo") +x.setData("merhaba") +assert(x.toString() == "merhaba") diff --git a/tests/iteration.py b/tests/iteration.py new file mode 100644 index 0000000..fd85ded --- /dev/null +++ b/tests/iteration.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +import piksemel as iks + +doc_xml = """ + + + + + + + blah & bleh + + + + + + + + + +""" + +doc = iks.parseString(doc_xml) + +count = 0 +for node in doc: + assert(node.type() == iks.TAG or node.type() == iks.DATA) + count += 1 +assert(count == 21) + +count = 0 +for node in doc.tags(): + assert(node.type() == iks.TAG) + count += 1 +assert(count == 10) + +count = 0 +for node in doc.tags("a"): + assert(node.type() == iks.TAG) + assert(node.name() == "a") + assert(node.getAttribute("foo") == None) + count += 1 +assert(count == 4) + +count = 0 +for node in doc.tags("item"): + assert(node.type() == iks.TAG) + assert(node.name() == "item") + count += 1 +assert(count == 4) diff --git a/tests/root.py b/tests/root.py new file mode 100644 index 0000000..f5673ae --- /dev/null +++ b/tests/root.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +import piksemel + +doc = piksemel.newDocument("lala") + +try: + doc.appendTag("lala") +except piksemel.NotTag: + pass + +try: + doc.appendData("lala") +except piksemel.NotTag: + pass + +try: + doc.prependTag("lala") +except piksemel.NotTag: + pass + +try: + doc.prependData("lala") +except piksemel.NotTag: + pass + diff --git a/tests/traverse.py b/tests/traverse.py new file mode 100644 index 0000000..6a9a6dc --- /dev/null +++ b/tests/traverse.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 + +import piksemel as iks + +doc_xml = """ + + + + + blah & bleh + + + + + + +""" + +doc = iks.parseString(doc_xml) + +# sibling nodes + +node = doc.firstChild() +last = None +count = 0 +while node: + last = node + count += 1 + node = node.next() +assert(count == 13) + +node = last +while node: + count -= 1 + node = node.previous() +assert(count == 0) + +# sibling tags + +item = doc.getTag("item") +last = None +while item: + last = item + assert(item.type() == iks.TAG) + assert(item.name() == "item") + count += 1 + item = item.nextTag("item") +assert(count == 4) + +item = last +while item: + assert(item.type() == iks.TAG) + assert(item.name() == "item") + count -= 1 + item = item.previousTag("item") +assert(count == 0) + +node = doc.getTag("item") +last = None +while node: + last = node + assert(node.type() == iks.TAG) + count += 1 + node = node.nextTag() +assert(count == 6) + +node = last +while node: + assert(node.type() == iks.TAG) + count -= 1 + node = node.previousTag() +assert(count == 0) + +# parent/child + +node = doc.getTag("item").nextTag().nextTag().getTag("nanuk") +assert(node.type() == iks.TAG) +assert(node.name() == "nanuk") + +item = node.parent() +assert(item.type() == iks.TAG) +assert(item.name() == "item") + +top = node.root() +assert(top.type() == iks.TAG) +assert(top.name() == "test")