diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c6e5b3ba..8ca6e08f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: msystem: UCRT64 update: true cache: true - install: 'base-devel mingw-w64-ucrt-x86_64-gcc msys/p7zip mingw-w64-ucrt-x86_64-qt5-base mingw-w64-ucrt-x86_64-qt5-3d mingw-w64-ucrt-x86_64-qt5-imageformats' + install: 'base-devel mingw-w64-ucrt-x86_64-gcc msys/p7zip mingw-w64-ucrt-x86_64-qt5-base mingw-w64-ucrt-x86_64-qt5-3d mingw-w64-ucrt-x86_64-qt5-imageformats mingw-w64-ucrt-x86_64-qt5-tools' - name: 'Build with qmake' shell: msys2 {0} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27ba85be..300a1b6c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: msystem: UCRT64 update: true cache: true - install: 'base-devel mingw-w64-ucrt-x86_64-gcc msys/p7zip mingw-w64-ucrt-x86_64-qt5-base mingw-w64-ucrt-x86_64-qt5-3d mingw-w64-ucrt-x86_64-qt5-imageformats' + install: 'base-devel mingw-w64-ucrt-x86_64-gcc msys/p7zip mingw-w64-ucrt-x86_64-qt5-base mingw-w64-ucrt-x86_64-qt5-3d mingw-w64-ucrt-x86_64-qt5-imageformats mingw-w64-ucrt-x86_64-qt5-tools' - name: 'Build with qmake' shell: msys2 {0} diff --git a/.gitmodules b/.gitmodules index b2b73ae0..c47c5ab1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "docsys"] - path = build/docsys - url = https://github.com/niftools/nifdocsys.git [submodule "qhull"] path = lib/qhull url = https://github.com/qhull/qhull.git @@ -10,3 +7,9 @@ [submodule "lib/libfo76utils"] path = lib/libfo76utils url = https://github.com/fo76utils/libfo76utils.git +[submodule "build/docsys/kfmxml"] + path = build/docsys/kfmxml + url = https://github.com/niftools/kfmxml.git +[submodule "build/docsys/nifxml"] + path = build/docsys/nifxml + url = https://github.com/niftools/nifxml.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b5819e0..925e59a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ == CHANGELOG == +* Vertex selection has been implemented for Starfield, and the maximum number of shapes supported by vertex selection has been increased from 256 to 32768 for all games. +* Fixed the 'Add Tangent Spaces and Update' spell for Starfield. +* Importing OBJ files to Skyrim SE, Fallout 4 and Fallout 76 models now creates BSTriShape geometry instead of NiTriShape. +* Added support for vertex colors in exported and imported OBJ files (BSTriShape only). +* Fixes to OBJ import. +* Disabled some of the spells for newer games where they are not implemented or should not be applicable (e.g. Fallout 4 and newer do not use triangle strips). + +#### NifSkope-2.0.dev9-20240804 + +* Implemented the 'Remove Duplicate Vertices' spell for Starfield, and fixed warnings about missing triangle data for older games. * Restored support for Oblivion, Fallout 3 and New Vegas shading. +* The UV editor now allows selecting Oblivion and Fallout 3/NV texture slots. +* New render setting for the number of importance samples to use at 512x512 and higher PBR cube map resolutions. The default is 1024, lower values reduce pre-filtering time at the cost of image quality. +* The view is automatically centered after importing a glTF file. +* Fixed saving screenshots in WebP, BMP and TGA formats. +* Fixed crash on double clicking block names in the block list. +* Fix to issue https://github.com/hexabits/nifskope/issues/68 +* Minor optimizations in the resource manager and renderer. #### NifSkope-2.0.dev9-20240724 diff --git a/NifSkope.pro b/NifSkope.pro index e61971a6..0de2c883 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -526,12 +526,6 @@ build_pass|!debug_and_release { ## QMAKE_POST_LINK ############################### -win32:contains(QT_ARCH, i386) { - DEP += \ - dep/NifMopp.dll - copyFiles( $$DEP ) -} - XML += \ build/nif.xml \ build/docsys/kfmxml/kfm.xml diff --git a/NifSkope_functions.pri b/NifSkope_functions.pri index bb22e5d1..d31375e9 100644 --- a/NifSkope_functions.pri +++ b/NifSkope_functions.pri @@ -191,13 +191,20 @@ defineReplace(QtBins) { $$[QT_INSTALL_BINS]/libgraphite2.dll \ $$[QT_INSTALL_BINS]/libharfbuzz-0.dll \ $$[QT_INSTALL_BINS]/libiconv-2.dll \ - $$[QT_INSTALL_BINS]/libicu*.dll \ + $$[QT_INSTALL_BINS]/libicudt*.dll \ + $$[QT_INSTALL_BINS]/libicuin*.dll \ + $$[QT_INSTALL_BINS]/libicuuc*.dll \ $$[QT_INSTALL_BINS]/libintl-8.dll \ $$[QT_INSTALL_BINS]/libjpeg-8.dll \ $$[QT_INSTALL_BINS]/libmd4c.dll \ - $$[QT_INSTALL_BINS]/libpcre2-*.dll \ + $$[QT_INSTALL_BINS]/libpcre2-16*.dll \ + $$[QT_INSTALL_BINS]/libpcre2-8*.dll \ $$[QT_INSTALL_BINS]/libpng16-*.dll \ + $$[QT_INSTALL_BINS]/libsharpyuv-*.dll \ $$[QT_INSTALL_BINS]/libstdc++-6.dll \ + $$[QT_INSTALL_BINS]/libwebp-*.dll \ + $$[QT_INSTALL_BINS]/libwebpdemux-*.dll \ + $$[QT_INSTALL_BINS]/libwebpmux-*.dll \ $$[QT_INSTALL_BINS]/libwinpthread-1.dll \ $$[QT_INSTALL_BINS]/libzstd.dll \ $$[QT_INSTALL_BINS]/zlib1.dll diff --git a/README.md b/README.md index 3ef9839d..d4399772 100644 --- a/README.md +++ b/README.md @@ -19,16 +19,21 @@ Binary packages for Windows and Linux can be downloaded from [Releases](https:// You can also download the latest official release from [niftools/nifskope](https://github.com/niftools/nifskope/releases), or development builds from [hexabits/nifskope](https://github.com/hexabits/nifskope/releases). -**Note:** running NifSkope under Wayland on Linux may require setting the QT\_QPA\_PLATFORM environment variable to "xcb": +**Notes:** + +Running NifSkope under Wayland on Linux may require setting the QT\_QPA\_PLATFORM environment variable to "xcb": QT_QPA_PLATFORM=xcb ./NifSkope +The resource manager in this version of NifSkope is optimized for PCs with solid-state drives. While hard disk drives generally also work, if the number of loose resources is large, load times can be significantly shorter on an SSD when the data is not cached yet by the operating system. + #### Building from source code Compiling NifSkope requires Qt 5.15. On Windows, [MSYS2](https://www.msys2.org/) can be used for building. After running the MSYS2 installer, use the following commands in the MSYS2-UCRT64 shell to install required packages: pacman -S base-devel mingw-w64-ucrt-x86_64-gcc - pacman -S mingw-w64-ucrt-x86_64-qt5-base mingw-w64-ucrt-x86_64-qt5-3d mingw-w64-ucrt-x86_64-qt5-imageformats + pacman -S mingw-w64-ucrt-x86_64-qt5-base mingw-w64-ucrt-x86_64-qt5-3d + pacman -S mingw-w64-ucrt-x86_64-qt5-imageformats mingw-w64-ucrt-x86_64-qt5-tools pacman -S git All installed MSYS2 packages can be updated anytime later by running the command '**pacman -Syu**'. To download the complete NifSkope source code, use '**git clone**' as follows: @@ -37,6 +42,7 @@ All installed MSYS2 packages can be updated anytime later by running the command Finally, run '**qmake**' and then '**make**' in MSYS2-UCRT64 to build the source code (the -j 8 option sets the number of processes to run in parallel). The resulting binaries and required DLL files and resources are placed under '**release**'. + cd nifskope qmake NifSkope.pro make -j 8 diff --git a/build/README.md.in b/build/README.md.in index ce7d340d..5fae514d 100644 --- a/build/README.md.in +++ b/build/README.md.in @@ -10,16 +10,21 @@ Binary packages for Windows and Linux can be downloaded from [Releases](https:// You can also download the latest official release from [niftools/nifskope](https://github.com/niftools/nifskope/releases), or development builds from [hexabits/nifskope](https://github.com/hexabits/nifskope/releases). -**Note:** running NifSkope under Wayland on Linux may require setting the QT\_QPA\_PLATFORM environment variable to "xcb": +**Notes:** + +Running NifSkope under Wayland on Linux may require setting the QT\_QPA\_PLATFORM environment variable to "xcb": QT_QPA_PLATFORM=xcb ./NifSkope +The resource manager in this version of NifSkope is optimized for PCs with solid-state drives. While hard disk drives generally also work, if the number of loose resources is large, load times can be significantly shorter on an SSD when the data is not cached yet by the operating system. + #### Building from source code Compiling NifSkope requires Qt 5.15. On Windows, [MSYS2](https://www.msys2.org/) can be used for building. After running the MSYS2 installer, use the following commands in the MSYS2-UCRT64 shell to install required packages: pacman -S base-devel mingw-w64-ucrt-x86_64-gcc - pacman -S mingw-w64-ucrt-x86_64-qt5-base mingw-w64-ucrt-x86_64-qt5-3d mingw-w64-ucrt-x86_64-qt5-imageformats + pacman -S mingw-w64-ucrt-x86_64-qt5-base mingw-w64-ucrt-x86_64-qt5-3d + pacman -S mingw-w64-ucrt-x86_64-qt5-imageformats mingw-w64-ucrt-x86_64-qt5-tools pacman -S git All installed MSYS2 packages can be updated anytime later by running the command '**pacman -Syu**'. To download the complete NifSkope source code, use '**git clone**' as follows: @@ -28,6 +33,7 @@ All installed MSYS2 packages can be updated anytime later by running the command Finally, run '**qmake**' and then '**make**' in MSYS2-UCRT64 to build the source code (the -j 8 option sets the number of processes to run in parallel). The resulting binaries and required DLL files and resources are placed under '**release**'. + cd nifskope qmake NifSkope.pro make -j 8 diff --git a/build/docsys b/build/docsys deleted file mode 160000 index 0ffd95a5..00000000 --- a/build/docsys +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0ffd95a50c277e761ab664b68a827502d163c083 diff --git a/build/docsys/.project b/build/docsys/.project new file mode 100644 index 00000000..d8d55d7d --- /dev/null +++ b/build/docsys/.project @@ -0,0 +1,17 @@ + + + nifdocsys + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/build/docsys/__init__.py b/build/docsys/__init__.py new file mode 100644 index 00000000..596cb5cd --- /dev/null +++ b/build/docsys/__init__.py @@ -0,0 +1 @@ +__all__ = ["nifxml"] diff --git a/build/docsys/doc/docsys.css b/build/docsys/doc/docsys.css new file mode 100644 index 00000000..018254bc --- /dev/null +++ b/build/docsys/doc/docsys.css @@ -0,0 +1,27 @@ +hr { border: 1px; color: black; background: black; height: 1px; } +body { font-family: Verdana, Arial, Helvetica, sans-serif; } +p, td, th, div, pre, li { color: black; font-size: 10pt; } +td { vertical-align: text-top; } +a { color: #006699; text-decoration: none;} +a:link { color: #006699; text-decoration: none;} +a:visited { color: #006699; text-decoration: none;} +a:active { text-decoration: none;} +a:hover { color: #DD6900; text-decoration: underline;} + +th { font-weight: bold; color: black; background: #D3DCE3} +h1 { text-align: center; color: black; background: #FF6600; } +h2 { text-align: center; color: black; background: #FFCC33; } +h3 { text-align: left; color: black; background: #FFCC66; } +a:link { text-decoration: none; color: #006699; background: transparent; } +a:visited { text-decoration: none; color: #006699; background: transparent; } +a:hover { text-decoration: underline; color: #DD6900; background: transparent; } +a:link.nav { color: #000000; background: transparent; } +a:visited.nav { color: #000000; background: transparent; } +a:hover.nav { color: #FF0000; background: transparent; } +.nav { color: #000000; background: transparent; } +tr.reg0 { background: #CCCCCC; color: black; } +tr.reg1 { background: #DDDDDD; color: black; } +tr.inact0 { background: #CCAAAA; color: black; } +tr.inact1 { background: #DDBBBB; color: black; } +tr.extrnl { background: #AADDAA; color: black; } + diff --git a/build/docsys/doc/favicon.ico b/build/docsys/doc/favicon.ico new file mode 100644 index 00000000..fc12e799 Binary files /dev/null and b/build/docsys/doc/favicon.ico differ diff --git a/build/docsys/epydoc.bat b/build/docsys/epydoc.bat new file mode 100644 index 00000000..fc351207 --- /dev/null +++ b/build/docsys/epydoc.bat @@ -0,0 +1,3 @@ +set PATH=%PATH%;C:\Program Files\Python24 + +python -c "import sys, os.path; from os.path import join; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'epydoc') ] + sys.path; script_path = os.path.abspath(sys.path[0]); sys.path = [p for p in sys.path if os.path.abspath(p) != script_path]; from epydoc.gui import gui; gui()" diff --git a/build/docsys/gen_niflib.py b/build/docsys/gen_niflib.py new file mode 100755 index 00000000..f7ce49f8 --- /dev/null +++ b/build/docsys/gen_niflib.py @@ -0,0 +1,907 @@ +#!/usr/bin/python + +# gen_niflib.py +# +# This script generates C++ code for Niflib. +# +# -------------------------------------------------------------------------- +# Command line options +# +# -p /path/to/niflib : specifies the path where niflib can be found +# +# -b : enable bootstrap mode (generates templates) +# +# -i : do NOT generate implmentation; place all code in defines.h +# +# -a : generate accessors for data in classes +# +# -n : generate only files which match the specified name +# +# -------------------------------------------------------------------------- +# ***** BEGIN LICENSE BLOCK ***** +# +# Copyright (c) 2005, NIF File Format Library and Tools +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# * Neither the name of the NIF File Format Library and Tools +# project nor the names of its contributors may be used to endorse +# or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# ***** END LICENSE BLOCK ***** +# -------------------------------------------------------------------------- + +from nifxml import * +from distutils.dir_util import mkpath +import os +import hashlib +import itertools + +# +# global data +# + +ROOT_DIR = ".." +BOOTSTRAP = False +GENIMPL = True +GENACCESSORS = False +GENBLOCKS = [] +GENALLFILES = True + +prev = "" +for i in sys.argv: + if prev == "-p": + ROOT_DIR = i + elif i == "-b": + BOOTSTRAP = True + elif i == "-i": + GENIMPL = False + elif i == "-a": + GENACCESSORS = True + elif prev == "-n": + GENBLOCKS.append(i) + GENALLFILES = False + + prev = i + + +# Fix known manual update attributes. For now hard code here. +block_types["NiKeyframeData"].find_member("Num Rotation Keys").is_manual_update = True +#block_types["NiTriStripsData"].find_member("Num Triangles").is_manual_update = True + +# +# Function to extract custom code from existing file +# +def ExtractCustomCode( file_name ): + custom_lines = {} + custom_lines['MISC'] = [] + custom_lines['FILE HEAD'] = [] + custom_lines['FILE FOOT'] = [] + custom_lines['PRE-READ'] = [] + custom_lines['POST-READ'] = [] + custom_lines['PRE-WRITE'] = [] + custom_lines['POST-WRITE'] = [] + custom_lines['PRE-STRING'] = [] + custom_lines['POST-STRING'] = [] + custom_lines['PRE-FIXLINKS'] = [] + custom_lines['POST-FIXLINKS'] = [] + custom_lines['CONSTRUCTOR'] = [] + custom_lines['DESTRUCTOR'] = [] + + if os.path.isfile( file_name ) == False: + custom_lines['MISC'].append( "\n" ) + custom_lines['FILE HEAD'].append( "\n" ) + custom_lines['FILE FOOT'].append( "\n" ) + custom_lines['PRE-READ'].append( "\n" ) + custom_lines['POST-READ'].append( "\n" ) + custom_lines['PRE-WRITE'].append( "\n" ) + custom_lines['POST-WRITE'].append( "\n" ) + custom_lines['PRE-STRING'].append( "\n" ) + custom_lines['POST-STRING'].append( "\n" ) + custom_lines['PRE-FIXLINKS'].append( "\n" ) + custom_lines['POST-FIXLINKS'].append( "\n" ) + custom_lines['CONSTRUCTOR'].append( "\n" ) + custom_lines['DESTRUCTOR'].append( "\n" ) + return custom_lines + + f = file( file_name, 'r' ) + lines = f.readlines() + f.close() + + custom_flag = False + custom_name = "" + + for l in lines: + if custom_flag == True: + if l.find( '//--END CUSTOM CODE--//' ) != -1: + custom_flag = False + else: + if not custom_lines[custom_name]: + custom_lines[custom_name] = [l] + else: + custom_lines[custom_name].append(l) + if l.find( '//--BEGIN MISC CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'MISC' + elif l.find( '//--BEGIN FILE HEAD CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'FILE HEAD' + elif l.find( '//--BEGIN FILE FOOT CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'FILE FOOT' + elif l.find( '//--BEGIN PRE-READ CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'PRE-READ' + elif l.find( '//--BEGIN POST-READ CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'POST-READ' + elif l.find( '//--BEGIN PRE-WRITE CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'PRE-WRITE' + elif l.find( '//--BEGIN POST-WRITE CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'POST-WRITE' + elif l.find( '//--BEGIN PRE-STRING CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'PRE-STRING' + elif l.find( '//--BEGIN POST-STRING CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'POST-STRING' + elif l.find( '//--BEGIN PRE-FIXLINKS CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'PRE-FIXLINKS' + elif l.find( '//--BEGIN POST-FIXLINKS CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'POST-FIXLINKS' + elif l.find( '//--BEGIN CONSTRUCTOR CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'CONSTRUCTOR' + elif l.find( '//--BEGIN DESTRUCTOR CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'DESTRUCTOR' + elif l.find( '//--BEGIN INCLUDE CUSTOM CODE--//' ) != -1: + custom_flag = True + custom_name = 'INCLUDE' + + return custom_lines + +# +# Function to compare two files +# + +def OverwriteIfChanged( original_file, candidate_file ): + files_differ = False + + if os.path.isfile( original_file ): + f1 = file( original_file, 'r' ) + f2 = file( candidate_file, 'r' ) + + s1 = f1.read() + s2 = f2.read() + + f1.close() + f2.close() + + if s1 != s2: + files_differ = True + #remove original file + os.unlink( original_file ) + else: + files_differ = True + + if files_differ: + #Files differ, so overwrite original with candidate + os.rename( candidate_file, original_file ) + +# +# generate compound code +# + +mkpath(os.path.join(ROOT_DIR, "include/obj")) +mkpath(os.path.join(ROOT_DIR, "include/gen")) + +mkpath(os.path.join(ROOT_DIR, "src/obj")) +mkpath(os.path.join(ROOT_DIR, "src/gen")) + +for n in compound_names: + x = compound_types[n] + + # skip natively implemented types + if x.niflibtype: continue + if n[:3] == 'ns ': continue + + if not GENALLFILES and not x.cname in GENBLOCKS: + continue + + #Get existing custom code + file_name = ROOT_DIR + '/include/gen/' + x.cname + '.h' + custom_lines = ExtractCustomCode( file_name ); + + h = CFile(file_name, 'w') + h.code( '/* Copyright (c) 2006, NIF File Format Library and Tools' ) + h.code( 'All rights reserved. Please see niflib.h for license. */' ) + h.code() + h.code( '//---THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT---//' ) + h.code() + h.code( '//To change this file, alter the niftools/docsys/gen_niflib.py Python script.' ) + h.code() + h.code( '#ifndef _' + x.cname.upper() + '_H_' ) + h.code( '#define _' + x.cname.upper() + '_H_' ) + h.code() + h.code( '#include "../NIF_IO.h"' ) + if n in ["Header", "Footer"]: + h.code( '#include "../obj/NiObject.h"' ) + h.code( x.code_include_h() ) + h.write( "namespace Niflib {\n" ) + h.code( x.code_fwd_decl() ) + h.code() + # header + h.comment(x.description) + hdr = "struct %s"%x.cname + if x.template: hdr = "template \n%s"%hdr + hdr += " {" + h.code(hdr) + + #constructor/destructor/assignment + if not x.template: + h.code( '/*! Default Constructor */' ) + h.code( "NIFLIB_API %s();"%x.cname ) + h.code( '/*! Default Destructor */' ) + h.code( "NIFLIB_API ~%s();"%x.cname ) + h.code( '/*! Copy Constructor */' ) + h.code( 'NIFLIB_API %s( const %s & src );'%(x.cname, x.cname) ) + h.code( '/*! Copy Operator */' ) + h.code( 'NIFLIB_API %s & operator=( const %s & src );'%(x.cname, x.cname) ) + + + + # declaration + h.declare(x) + + # header and footer functions + if n == "Header": + h.code( 'NIFLIB_HIDDEN NifInfo Read( istream& in );' ) + h.code( 'NIFLIB_HIDDEN void Write( ostream& out, const NifInfo & info = NifInfo() ) const;' ) + h.code( 'NIFLIB_HIDDEN string asString( bool verbose = false ) const;' ) + + if n == "Footer": + h.code( 'NIFLIB_HIDDEN void Read( istream& in, list & link_stack, const NifInfo & info );' ) + h.code( 'NIFLIB_HIDDEN void Write( ostream& out, const map & link_map, list & missing_link_stack, const NifInfo & info ) const;' ) + h.code( 'NIFLIB_HIDDEN string asString( bool verbose = false ) const;' ) + + h.code( '//--BEGIN MISC CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['MISC']: + h.write(l); + + h.code( '//--END CUSTOM CODE--//' ) + + # done + h.code("};") + h.code() + h.write( "}\n" ) + h.code( '#endif' ) + h.close() + + if not x.template: + #Get existing custom code + file_name = ROOT_DIR + '/src/gen/' + x.cname + '.cpp' + custom_lines = ExtractCustomCode( file_name ); + + cpp = CFile(file_name, 'w') + cpp.code( '/* Copyright (c) 2006, NIF File Format Library and Tools' ) + cpp.code( 'All rights reserved. Please see niflib.h for license. */' ) + cpp.code() + cpp.code( '//---THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT---//' ) + cpp.code() + cpp.code( '//To change this file, alter the niftools/docsys/gen_niflib.py Python script.' ) + cpp.code() + cpp.code( x.code_include_cpp( True, "../../include/gen/", "../../include/obj/" ) ) + cpp.code( "using namespace Niflib;" ) + cpp.code() + cpp.code( '//Constructor' ) + + # constructor + x_code_construct = x.code_construct() + #if x_code_construct: + cpp.code("%s::%s()"%(x.cname,x.cname) + x_code_construct + " {};") + cpp.code() + + cpp.code('//Copy Constructor') + cpp.code( '%s::%s( const %s & src ) {'%(x.cname,x.cname,x.cname) ) + cpp.code( '*this = src;' ) + cpp.code('};') + cpp.code() + + cpp.code('//Copy Operator') + cpp.code( '%s & %s::operator=( const %s & src ) {'%(x.cname,x.cname,x.cname) ) + for m in x.members: + if not m.is_duplicate: + cpp.code('this->%s = src.%s;'%(m.cname, m.cname) ) + cpp.code('return *this;') + cpp.code('};') + cpp.code() + + cpp.code( '//Destructor' ) + + # destructor + cpp.code("%s::~%s()"%(x.cname,x.cname) + " {};") + + # header and footer functions + if n == "Header": + cpp.code( 'NifInfo ' + x.cname + '::Read( istream& in ) {' ) + cpp.code( '//Declare NifInfo structure' ) + cpp.code( 'NifInfo info;' ) + cpp.code() + cpp.stream(x, ACTION_READ) + cpp.code() + cpp.code( '//Copy info.version to local version var.' ) + cpp.code( 'version = info.version;' ) + cpp.code() + cpp.code( '//Fill out and return NifInfo structure.' ) + cpp.code( 'info.userVersion = userVersion;' ) + cpp.code( 'info.userVersion2 = userVersion2;' ) + cpp.code( 'info.endian = EndianType(endianType);' ) + cpp.code( 'info.creator = exportInfo.creator.str;' ) + cpp.code( 'info.exportInfo1 = exportInfo.exportInfo1.str;' ) + cpp.code( 'info.exportInfo2 = exportInfo.exportInfo2.str;' ) + cpp.code() + cpp.code( 'return info;' ) + cpp.code() + cpp.code( '}' ) + cpp.code() + cpp.code( 'void ' + x.cname + '::Write( ostream& out, const NifInfo & info ) const {' ) + cpp.stream(x, ACTION_WRITE) + cpp.code( '}' ) + cpp.code() + cpp.code( 'string ' + x.cname + '::asString( bool verbose ) const {' ) + cpp.stream(x, ACTION_OUT) + cpp.code( '}' ) + + if n == "Footer": + cpp.code() + cpp.code( 'void ' + x.cname + '::Read( istream& in, list & link_stack, const NifInfo & info ) {' ) + cpp.stream(x, ACTION_READ) + cpp.code( '}' ) + cpp.code() + cpp.code( 'void ' + x.cname + '::Write( ostream& out, const map & link_map, list & missing_link_stack, const NifInfo & info ) const {' ) + cpp.stream(x, ACTION_WRITE) + cpp.code( '}' ) + cpp.code() + cpp.code( 'string ' + x.cname + '::asString( bool verbose ) const {' ) + cpp.stream(x, ACTION_OUT) + cpp.code( '}' ) + + cpp.code() + cpp.code( '//--BEGIN MISC CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['MISC']: + cpp.write(l); + + cpp.code( '//--END CUSTOM CODE--//' ) + + cpp.close() + + # Write out Public Enumeration header Enumerations +if GENALLFILES: + out = CFile(ROOT_DIR + '/include/gen/enums.h', 'w') + out.code( '/* Copyright (c) 2006, NIF File Format Library and Tools' ) + out.code( 'All rights reserved. Please see niflib.h for license. */' ) + out.code('#ifndef _NIF_ENUMS_H_') + out.code('#define _NIF_ENUMS_H_') + out.code() + out.code( '//---THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT---//' ) + out.code() + out.code( '//To change this file, alter the niftools/docsys/gen_niflib.py Python script.' ) + out.code() + out.code( '#include ' ) + out.code( 'using namespace std;' ) + out.code() + out.write('namespace Niflib {\n') + out.code() + for n, x in itertools.chain(enum_types.iteritems(), flag_types.iteritems()): + if x.options: + if x.description: + out.comment(x.description) + out.code('enum %s {'%(x.cname)) + for o in x.options: + out.code('%s = %s, /*!< %s */'%(o.cname, o.value, o.description)) + out.code('};') + out.code() + out.code('ostream & operator<<( ostream & out, %s const & val );'%x.cname) + out.code() + + out.write('}\n') + out.code('#endif') + out.close() + + # Write out Internal Enumeration header (NifStream functions) +if GENALLFILES: + out = CFile(ROOT_DIR + '/include/gen/enums_intl.h', 'w') + out.code( '/* Copyright (c) 2006, NIF File Format Library and Tools' ) + out.code( 'All rights reserved. Please see niflib.h for license. */' ) + out.code() + out.code( '//---THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT---//' ) + out.code() + out.code( '//To change this file, alter the niftools/docsys/gen_niflib.py Python script.' ) + out.code() + out.code('#ifndef _NIF_ENUMS_INTL_H_') + out.code('#define _NIF_ENUMS_INTL_H_') + out.code() + out.code( '#include ' ) + out.code( 'using namespace std;' ) + out.code() + out.code('#include "../nif_basic_types.h"') + out.code() + out.write('namespace Niflib {\n') + out.code() + for n, x in itertools.chain(enum_types.iteritems(), flag_types.iteritems()): + if x.options: + if x.description: + out.code() + out.code( '//---' + x.cname + '---//') + out.code() + out.code('void NifStream( %s & val, istream& in, const NifInfo & info = NifInfo() );'%x.cname) + out.code('void NifStream( %s const & val, ostream& out, const NifInfo & info = NifInfo() );'%x.cname) + out.code() + + out.write('}\n') + out.code('#endif') + out.close() + + #Write out Enumeration Implementation +if GENALLFILES: + out = CFile(ROOT_DIR + '/src/gen/enums.cpp', 'w') + out.code( '/* Copyright (c) 2006, NIF File Format Library and Tools' ) + out.code( 'All rights reserved. Please see niflib.h for license. */' ) + out.code() + out.code( '//---THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT---//' ) + out.code() + out.code( '//To change this file, alter the niftools/docsys/gen_niflib.py Python script.' ) + out.code() + out.code('#include ') + out.code('#include ') + out.code('#include "../../include/NIF_IO.h"') + out.code('#include "../../include/gen/enums.h"') + out.code('#include "../../include/gen/enums_intl.h"') + out.code() + out.code('using namespace std;') + out.code() + out.write('namespace Niflib {\n') + out.code() + + out.code() + for n, x in itertools.chain(enum_types.iteritems(), flag_types.iteritems()): + if x.options: + out.code() + out.code('//--' + x.cname + '--//') + out.code() + out.code('void NifStream( %s & val, istream& in, const NifInfo & info ) {'%(x.cname)) + out.code('%s temp;'%(x.storage)) + out.code('NifStream( temp, in, info );') + out.code('val = %s(temp);'%(x.cname)) + out.code('}') + out.code() + out.code('void NifStream( %s const & val, ostream& out, const NifInfo & info ) {'%(x.cname)) + out.code('NifStream( (%s)(val), out, info );'%(x.storage)) + out.code('}') + out.code() + out.code('ostream & operator<<( ostream & out, %s const & val ) { '%(x.cname)) + out.code('switch ( val ) {') + for o in x.options: + out.code('case %s: return out << "%s";'%(o.cname, o.name)) + out.code('default: return out << "Invalid Value! - " << (unsigned int)(val);') + out.code('}') + out.code('}') + out.code() + + out.write('}\n') + out.close() + + # + # NiObject Registration Function + # + out = CFile(ROOT_DIR + '/src/gen/register.cpp', 'w') + out.code( '/* Copyright (c) 2006, NIF File Format Library and Tools' ) + out.code( 'All rights reserved. Please see niflib.h for license. */' ) + out.code() + out.code( '//---THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT---//' ) + out.code() + out.code( '//To change this file, alter the niftools/docsys/gen_niflib.py Python script.' ) + out.code() + out.code( '#include "../../include/ObjectRegistry.h"' ) + for n in block_names: + x = block_types[n] + out.code( '#include "../../include/obj/' + x.cname + '.h"' ) + out.code() + out.code( 'namespace Niflib {' ) + out.code( 'void RegisterObjects() {' ) + out.code() + for n in block_names: + x = block_types[n] + out.code( 'ObjectRegistry::RegisterObject( "' + x.cname + '", ' + x.cname + '::Create );' ) + out.code() + out.code( '}' ) + out.code( '}' ) + out.close() + + +# +# NiObject Files +# +for n in block_names: + x = block_types[n] + x_define_name = define_name(x.cname) + + if not GENALLFILES and not x.cname in GENBLOCKS: + continue + + # + # NiObject Header File + # + + #Get existing custom code + file_name = ROOT_DIR + '/include/obj/' + x.cname + '.h' + custom_lines = ExtractCustomCode( file_name ); + + #output new file + out = CFile(file_name, 'w') + out.code( '/* Copyright (c) 2006, NIF File Format Library and Tools' ) + out.code( 'All rights reserved. Please see niflib.h for license. */' ) + out.code() + out.code( '//-----------------------------------NOTICE----------------------------------//' ) + out.code( '// Some of this file is automatically filled in by a Python script. Only //' ) + out.code( '// add custom code in the designated areas or it will be overwritten during //' ) + out.code( '// the next update. //' ) + out.code( '//-----------------------------------NOTICE----------------------------------//' ) + out.code() + out.code( '#ifndef _' + x.cname.upper() + '_H_' ) + out.code( '#define _' + x.cname.upper() + '_H_' ) + out.code() + out.code( '//--BEGIN FILE HEAD CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['FILE HEAD']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + out.code() + out.code( x.code_include_h() ) + out.write( "namespace Niflib {\n" ) + if not x.inherit: + out.code( 'using namespace std;' ) + out.code( x.code_fwd_decl() ) + out.code( 'class ' + x.cname + ';' ) + out.code( 'typedef Ref<' + x.cname + '> ' + x.cname + 'Ref;' ) + out.code() + out.comment( x.description ) + if x.inherit: + out.code( 'class ' + x.cname + ' : public ' + x.inherit.cname + ' {' ) + else: + out.code( 'class ' + x.cname + ' : public RefObject {' ) + out.code( 'public:' ) + out.code( '/*! Constructor */' ) + out.code( 'NIFLIB_API ' + x.cname + '();' ) + out.code() + out.code( '/*! Destructor */' ) + out.code( 'NIFLIB_API virtual ~' + x.cname + '();' ) + out.code() + out.code( '/*!' ) + out.code( ' * A constant value which uniquly identifies objects of this type.' ) + out.code( ' */' ) + out.code( 'NIFLIB_API static const Type TYPE;' ) + out.code() + out.code( '/*!' ) + out.code( ' * A factory function used during file reading to create an instance of this type of object.' ) + out.code( ' * \\return A pointer to a newly allocated instance of this type of object.' ) + out.code( ' */' ) + out.code( 'NIFLIB_API static NiObject * Create();' ) + out.code() + out.code( '/*!' ) + out.code( ' * Summarizes the information contained in this object in English.' ) + out.code( ' * \\param[in] verbose Determines whether or not detailed information about large areas of data will be printed out.' ) + out.code( ' * \\return A string containing a summary of the information within the object in English. This is the function that Niflyze calls to generate its analysis, so the output is the same.' ) + out.code( ' */' ) + out.code( 'NIFLIB_API virtual string asString( bool verbose = false ) const;' ) + out.code() + out.code( '/*!' ) + out.code( ' * Used to determine the type of a particular instance of this object.' ) + out.code( ' * \\return The type constant for the actual type of the object.' ) + out.code( ' */' ) + out.code( 'NIFLIB_API virtual const Type & GetType() const;' ) + out.code() + + # + # Show example naive implementation if requested + # + + # Create a list of members eligable for functions + if GENACCESSORS: + func_members = [] + for y in x.members: + if not y.arr1_ref and not y.arr2_ref and y.cname.lower().find("unk") == -1: + func_members.append(y) + + if len(func_members) > 0: + out.code( '/***Begin Example Naive Implementation****' ) + out.code() + for y in func_members: + out.comment( y.description + "\n\\return The current value.", False ) + out.code( y.getter_declare("", ";") ) + out.code() + out.comment( y.description + "\n\\param[in] value The new value.", False ) + out.code( y.setter_declare("", ";") ) + out.code() + out.code( '****End Example Naive Implementation***/' ) + else: + out.code ( '//--This object has no eligable attributes. No example implementation generated--//' ) + out.code() + + out.code( '//--BEGIN MISC CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['MISC']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + if x.members: + out.code( 'protected:' ) + out.declare(x) + out.code( 'public:' ) + out.code( '/*! NIFLIB_HIDDEN function. For internal use only. */' ) + out.code( 'NIFLIB_HIDDEN virtual void Read( istream& in, list & link_stack, const NifInfo & info );' ) + out.code( '/*! NIFLIB_HIDDEN function. For internal use only. */' ) + out.code( 'NIFLIB_HIDDEN virtual void Write( ostream& out, const map & link_map, list & missing_link_stack, const NifInfo & info ) const;' ) + out.code( '/*! NIFLIB_HIDDEN function. For internal use only. */' ) + out.code( 'NIFLIB_HIDDEN virtual void FixLinks( const map & objects, list & link_stack, list & missing_link_stack, const NifInfo & info );' ) + out.code( '/*! NIFLIB_HIDDEN function. For internal use only. */' ) + out.code( 'NIFLIB_HIDDEN virtual list GetRefs() const;' ) + out.code( '/*! NIFLIB_HIDDEN function. For internal use only. */' ) + out.code( 'NIFLIB_HIDDEN virtual list GetPtrs() const;' ) + out.code( '};' ) + out.code() + out.code( '//--BEGIN FILE FOOT CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['FILE FOOT']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + out.code() + out.write( "} //End Niflib namespace\n" ) + out.code( '#endif' ) + out.close() + + ##Check if the temp file is identical to the target file + #OverwriteIfChanged( file_name, 'temp' ) + + # + # NiObject Implementation File + # + + #Get existing custom code + file_name = ROOT_DIR + '/src/obj/' + x.cname + '.cpp' + custom_lines = ExtractCustomCode( file_name ); + + out = CFile( file_name, 'w') + out.code( '/* Copyright (c) 2006, NIF File Format Library and Tools' ) + out.code( 'All rights reserved. Please see niflib.h for license. */' ) + out.code() + out.code( '//-----------------------------------NOTICE----------------------------------//' ) + out.code( '// Some of this file is automatically filled in by a Python script. Only //' ) + out.code( '// add custom code in the designated areas or it will be overwritten during //' ) + out.code( '// the next update. //' ) + out.code( '//-----------------------------------NOTICE----------------------------------//' ) + out.code() + out.code( '//--BEGIN FILE HEAD CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['FILE HEAD']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + out.code() + out.code( '#include "../../include/FixLink.h"' ) + out.code( '#include "../../include/ObjectRegistry.h"' ) + out.code( '#include "../../include/NIF_IO.h"' ) + out.code( x.code_include_cpp( True, "../../include/gen/", "../../include/obj/" ) ) + out.code( "using namespace Niflib;" ); + out.code() + out.code( '//Definition of TYPE constant' ) + if x.inherit: + out.code ( 'const Type ' + x.cname + '::TYPE(\"' + x.cname + '\", &' + x.inherit.cname + '::TYPE );' ) + else: + out.code ( 'const Type ' + x.cname + '::TYPE(\"' + x.cname + '\", &RefObject::TYPE );' ) + out.code() + x_code_construct = x.code_construct() + if x_code_construct: + out.code( x.cname + '::' + x.cname + '()' + x_code_construct + ' {' ) + else: + out.code( x.cname + '::' + x.cname + '() {' ) + out.code ( '//--BEGIN CONSTRUCTOR CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['CONSTRUCTOR']: + out.write(l); + + out.code ( '//--END CUSTOM CODE--//') + out.code ( '}' ) + + out.code() + out.code( x.cname + '::' + '~' + x.cname + '() {' ) + out.code ( '//--BEGIN DESTRUCTOR CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['DESTRUCTOR']: + out.write(l); + + out.code ( '//--END CUSTOM CODE--//') + out.code ( '}' ) + out.code() + out.code( 'const Type & %s::GetType() const {'%x.cname ) + out.code( 'return TYPE;' ) + out.code( '}' ) + out.code() + out.code( 'NiObject * ' + x.cname + '::Create() {' ) + out.code( 'return new ' + x.cname + ';' ) + out.code( '}' ) + out.code() + + out.code("void %s::Read( istream& in, list & link_stack, const NifInfo & info ) {"%x.cname) + out.code( '//--BEGIN PRE-READ CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['PRE-READ']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + out.code() + out.stream(x, ACTION_READ) + out.code() + out.code( '//--BEGIN POST-READ CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['POST-READ']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + out.code("}") + out.code() + + out.code("void %s::Write( ostream& out, const map & link_map, list & missing_link_stack, const NifInfo & info ) const {"%x.cname) + out.code( '//--BEGIN PRE-WRITE CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['PRE-WRITE']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + out.code() + out.stream(x, ACTION_WRITE) + out.code() + out.code( '//--BEGIN POST-WRITE CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['POST-WRITE']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + out.code("}") + out.code() + + out.code("std::string %s::asString( bool verbose ) const {"%x.cname) + out.code( '//--BEGIN PRE-STRING CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['PRE-STRING']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + out.code() + out.stream(x, ACTION_OUT) + out.code() + out.code( '//--BEGIN POST-STRING CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['POST-STRING']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + out.code("}") + out.code() + + out.code("void %s::FixLinks( const map & objects, list & link_stack, list & missing_link_stack, const NifInfo & info ) {"%x.cname) + + out.code( '//--BEGIN PRE-FIXLINKS CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['PRE-FIXLINKS']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + out.code() + out.stream(x, ACTION_FIXLINKS) + out.code() + out.code( '//--BEGIN POST-FIXLINKS CUSTOM CODE--//' ) + #Preserve Custom code from before + for l in custom_lines['POST-FIXLINKS']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + out.code("}") + out.code() + + out.code("std::list %s::GetRefs() const {"%x.cname) + out.stream(x, ACTION_GETREFS) + out.code("}") + out.code() + + out.code("std::list %s::GetPtrs() const {"%x.cname) + out.stream(x, ACTION_GETPTRS) + out.code("}") + out.code() + + # Output example implementation of public getter/setter Mmthods if requested + if GENACCESSORS: + func_members = [] + for y in x.members: + if not y.arr1_ref and not y.arr2_ref and y.cname.lower().find("unk") == -1: + func_members.append(y) + + if len(func_members) > 0: + out.code( '/***Begin Example Naive Implementation****' ) + out.code() + for y in func_members: + out.code( y.getter_declare(x.name + "::", " {") ) + out.code( "return %s;"%y.cname ) + out.code( "}" ) + out.code() + + out.code( y.setter_declare(x.name + "::", " {") ) + out.code( "%s = value;"%y.cname ) + out.code( "}" ) + out.code() + out.code( '****End Example Naive Implementation***/' ) + else: + out.code ( '//--This object has no eligable attributes. No example implementation generated--//' ) + out.code() + + out.code( '//--BEGIN MISC CUSTOM CODE--//' ) + + #Preserve Custom code from before + for l in custom_lines['MISC']: + out.write(l); + + out.code( '//--END CUSTOM CODE--//' ) + + ##Check if the temp file is identical to the target file + #OverwriteIfChanged( file_name, 'temp' ) + + out.close() diff --git a/build/docsys/kfmxml b/build/docsys/kfmxml new file mode 160000 index 00000000..91eff92d --- /dev/null +++ b/build/docsys/kfmxml @@ -0,0 +1 @@ +Subproject commit 91eff92daf197f0c5376740ed4b000d064138ec2 diff --git a/build/docsys/nifxml b/build/docsys/nifxml new file mode 160000 index 00000000..970a6238 --- /dev/null +++ b/build/docsys/nifxml @@ -0,0 +1 @@ +Subproject commit 970a6238218a106daaeb89a61bcda0eeaf9d08c4 diff --git a/build/docsys/nifxml.py b/build/docsys/nifxml.py new file mode 100644 index 00000000..9559ddb4 --- /dev/null +++ b/build/docsys/nifxml.py @@ -0,0 +1,1801 @@ +# TODO: split in multiple files + +""" +This module generates C++ code for Niflib from the NIF file format specification XML. + +@author: Amorilia +@author: Shon + +@contact: http://niftools.sourceforge.net + +@copyright: +Copyright (c) 2005, NIF File Format Library and Tools. +All rights reserved. + +@license: +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + - Neither the name of the NIF File Format Library and Tools + project nor the names of its contributors may be used to endorse + or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +@var native_types: Maps name of basic or compound type to name of type implemented manually in Niflib. + These are the types tagged by the niflibtype tag in the XML. For example, + if a (basic or compound) type with C{name="ferrari"} has C{niflibtype="car"} + then C{native_types["ferrari"]} equals the string C{"car"}. +@type native_types: C{dictionary} + +@var basic_types: Maps name of basic type to L{Basic} instance. +@type basic_types: C{dictionary} + +@var compound_types: Maps name of compound type to a L{Compound} instance. +@type compound_types: C{dictionary} + +@var block_types: Maps name of the block name to a L{Block} instance. +@type block_types: C{list} + +@var basic_names: Sorted keys of L{basic_types}. +@type basic_names: C{list} + +@var compound_names: Sorted keys of L{compound_types}. +@type compound_names: C{list} + +@var block_names: Sorted keys of L{block_types}. +@type block_names: C{list} + +@var ACTION_READ: Constant for use with CFile::stream. Causes it to generate Niflib's Read function. +@type ACTION_READ: C{int} + +@var ACTION_WRITE: Constant for use with CFile::stream. Causes it to generate Niflib's Write function. +@type ACTION_WRITE: C{int} + +@var ACTION_OUT: Constant for use with CFile::stream. Causes it to generate Niflib's asString function. +@type ACTION_OUT: C{int} + +@var ACTION_FIXLINKS: Constant for use with CFile::stream. Causes it to generate Niflib's FixLinks function. +@type ACTION_FIXLINKS: C{int} + +@var ACTION_GETREFS: Constant for use with CFile::stream. Causes it to generate Niflib's GetRefs function. +@type ACTION_GETREFS: C{int} + +@var ACTION_GETPTRS: Constant for use with CFile::stream. Causes it to generate Niflib's GetPtrs function. +@type ACTION_GETPTRS: C{int} +""" + +from xml.dom.minidom import * +from textwrap import fill + +import sys +import os +import re +import types + +# +# global data +# + +native_types = {} +native_types['TEMPLATE'] = 'T' +basic_types = {} +enum_types = {} +flag_types = {} +compound_types = {} +block_types = {} +version_types = {} + +basic_names = [] +compound_names = [] +enum_names = [] +flag_names = [] +block_names = [] +version_names = [] + +ACTION_READ = 0 +ACTION_WRITE = 1 +ACTION_OUT = 2 +ACTION_FIXLINKS = 3 +ACTION_GETREFS = 4 +ACTION_GETPTRS = 5 + +# +# HTML Template class +# + +class Template: + """ + This class processes template files. These files have tags enclosed + in curly brackets like this: {tag}, which are replaced when a template + is processed. + """ + def __init__(self): + #Initialize variable dictionary + self.vars = {} + + def set_var(self, var_name, value): + self.vars[var_name] = value + + def parse(self, file_name): + #Open file and read contents to txt variable + f = file(file_name, 'r') + txt = f.read() + f.close() + + #Loop through all variables, replacing them in the template text + for i in self.vars: + txt = txt.replace( "{" + i + "}", str(self.vars[i]) ) + + #return result + return txt + +# +# C++ code formatting functions +# + +class CFile(file): + """ + This class represents a C++ source file. It is used to open the file for output + and automatically handles indentation by detecting brackets and colons. + It also handles writing the generated Niflib C++ code. + @ivar indent: The current level of indentation. + @type indent: int + @ivar backslash_mode: Determines whether a backslash is appended to each line for creation of multi-line defines + @type backslash_mode: bool + """ + def __init__(self, filename, mode): + """ + This constructor requires the name of the file to open and the IO mode to open it in. + @param filename: The name of the ouput file to open + @type filename: string + @param mode: The IO Mode. Same as fopen? Usually should be 'r', 'w', or 'a' + @type mode: char + """ + file.__init__(self, filename, mode) + self.indent = 0 + self.backslash_mode = False + + + def code(self, txt = None): + r""" + Formats a line of C++ code; the returned result always ends with a newline. + If txt starts with "E{rb}", indent is decreased, if it ends with "E{lb}", indent is increased. + Text ending in "E{:}" de-indents itself. For example "publicE{:}" + Result always ends with a newline + @param txt: None means just a line break. This will also break the backslash, which is kind of handy. + "\n" will create a backslashed newline in backslash mode. + @type txt: string, None + """ + # txt + # this will also break the backslash, which is kind of handy + # call code("\n") if you want a backslashed newline in backslash mode + if txt == None: + self.write("\n") + return + + # block end + if txt[:1] == "}": self.indent -= 1 + # special, private:, public:, and protected: + if txt[-1:] == ":": self.indent -= 1 + # endline string + if self.backslash_mode: + endl = " \\\n" + else: + endl = "\n" + # indent string + prefix = "\t" * self.indent + # strip trailing whitespace, including newlines + txt = txt.rstrip() + # indent, and add newline + result = prefix + txt.replace("\n", endl + prefix) + endl + # block start + if txt[-1:] == "{": self.indent += 1 + # special, private:, public:, and protected: + if txt[-1:] == ":": self.indent += 1 + + self.write(result) + + + # + def comment(self, txt, doxygen = True): + """ + Wraps text in C++ comments and outputs it to the file. Handles multilined comments as well. + Result always ends with a newline + @param txt: The text to enclose in a Doxygen comment + @type txt: string + """ + + # skip comments when we are in backslash mode + if self.backslash_mode: return + + lines = txt.split( '\n' ) + + txt = "" + for l in lines: + txt = txt + fill(l, 80) + "\n" + + txt = txt.strip() + + num_line_ends = txt.count( '\n' ) + + + if doxygen: + if num_line_ends > 0: + txt = txt.replace("\n", "\n * ") + self.code("/*!\n * " + txt + "\n */") + else: + self.code("/*! " + txt + " */" ) + else: + lines = txt.split('\n') + for l in lines: + self.code( "// " + l ) + + def declare(self, block): + """ + Formats the member variables for a specific class as described by the XML and outputs the result to the file. + @param block: The class or struct to generate member functions for. + @type block: Block, Compound + """ + if isinstance(block, Block): + #self.code('protected:') + prot_mode = True + for y in block.members: + if not y.is_duplicate: + if isinstance(block, Block): + if y.is_public and prot_mode: + self.code('public:') + prot_mode = False + elif not y.is_public and not prot_mode: + self.code('protected:') + prot_mode = True + self.comment(y.description) + self.code(y.code_declare()) + if y.func: + self.comment(y.description) + self.code("%s %s() const;"%(y.ctype,y.func)) + + def stream(self, block, action, localprefix = "", prefix = "", arg_prefix = "", arg_member = None): + """ + Generates the function code for various functions in Niflib and outputs it to the file. + @param block: The class or struct to generate the function for. + @type block: Block, Compound + @param action: The type of function to generate, valid values are:: + ACTION_READ - Read function. + ACTION_WRITE - Write function + ACTION_OUT - asString function + ACTION_FIXLINKS - FixLinks function + ACTION_GETREFS - GetRefs function + ACTION_GETPTRS - GetPtrs function + @type action: ACTION_X constant + @param localprefix: ? + @type localprefix: string + @param prefix: ? + @type prefix: string + @param arg_prefix: ? + @type arg_prefix: string + @param arg_member: ? + @type arg_member: None, ? + """ + lastver1 = None + lastver2 = None + lastuserver = None + lastcond = None + lastvercond = None + # stream name + if action == ACTION_READ: + stream = "in" + else: + stream = "out" + + + # preperation + if isinstance(block, Block) or block.name in ["Footer", "Header"]: + if action == ACTION_READ: + if block.has_links or block.has_crossrefs: + self.code("unsigned int block_num;") + if action == ACTION_OUT: + self.code("stringstream out;") + # declare array_output_count, only if it will actually be used + for y in block.members: + if y.arr1.lhs or (y.ctype in ["BoundingVolume", "ByteArray", "KeyGroup"]): + self.code("unsigned int array_output_count = 0;") + break + if action == ACTION_GETREFS: + self.code("list > refs;") + if action == ACTION_GETPTRS: + self.code("list ptrs;") + + # stream the ancestor + if isinstance(block, Block): + if block.inherit: + if action == ACTION_READ: + self.code("%s::Read( %s, link_stack, info );"%(block.inherit.cname, stream)) + elif action == ACTION_WRITE: + self.code("%s::Write( %s, link_map, missing_link_stack, info );"%(block.inherit.cname, stream)) + elif action == ACTION_OUT: + self.code("%s << %s::asString();"%(stream, block.inherit.cname)) + elif action == ACTION_FIXLINKS: + self.code("%s::FixLinks( objects, link_stack, missing_link_stack, info );"%block.inherit.cname) + elif action == ACTION_GETREFS: + self.code("refs = %s::GetRefs();"%block.inherit.cname) + elif action == ACTION_GETPTRS: + self.code("ptrs = %s::GetPtrs();"%block.inherit.cname) + + # declare and calculate local variables (TODO: GET RID OF THIS; PREFERABLY NO LOCAL VARIABLES AT ALL) + if action in [ACTION_READ, ACTION_WRITE, ACTION_OUT]: + block.members.reverse() # calculated data depends on data further down the structure + for y in block.members: + if not y.is_duplicate and not y.is_manual_update and action in [ACTION_WRITE, ACTION_OUT]: + if y.func: + self.code('%s%s = %s%s();'%(prefix, y.cname, prefix, y.func)) + elif y.is_calculated: + if action in [ACTION_READ, ACTION_WRITE]: + self.code('%s%s = %s%sCalc(info);'%(prefix, y.cname, prefix, y.cname)) + # ACTION_OUT is in asString(), which doesn't take version info + # so let's simply not print the field in this case + elif y.arr1_ref: + if not y.arr1 or not y.arr1.lhs: # Simple Scalar + cref = block.find_member(y.arr1_ref[0], True) + # if not cref.is_duplicate and not cref.next_dup and (not cref.cond.lhs or cref.cond.lhs == y.name): + # self.code('assert(%s%s == (%s)(%s%s.size()));'%(prefix, y.cname, y.ctype, prefix, cref.cname)) + self.code('%s%s = (%s)(%s%s.size());'%(prefix, y.cname, y.ctype, prefix, cref.cname)) + elif y.arr2_ref: # 1-dimensional dynamic array + cref = block.find_member(y.arr2_ref[0], True) + if not y.arr1 or not y.arr1.lhs: # Second dimension + # if not cref.is_duplicate and not cref.next_dup (not cref.cond.lhs or cref.cond.lhs == y.name): + # self.code('assert(%s%s == (%s)((%s%s.size() > 0) ? %s%s[0].size() : 0));'%(prefix, y.cname, y.ctype, prefix, cref.cname, prefix, cref.cname)) + self.code('%s%s = (%s)((%s%s.size() > 0) ? %s%s[0].size() : 0);'%(prefix, y.cname, y.ctype, prefix, cref.cname, prefix, cref.cname)) + else: + # index of dynamically sized array + self.code('for (unsigned int i%i = 0; i%i < %s%s.size(); i%i++)'%(self.indent, self.indent, prefix, cref.cname, self.indent)) + self.code('\t%s%s[i%i] = (%s)(%s%s[i%i].size());'%(prefix, y.cname, self.indent, y.ctype, prefix, cref.cname, self.indent)) + # else: #has duplicates needs to be selective based on version + # self.code('assert(!"%s");'%(y.name)) + block.members.reverse() # undo reverse + + + # now comes the difficult part: processing all members recursively + for y in block.members: + # get block + if y.type in basic_types: + subblock = basic_types[y.type] + elif y.type in compound_types: + subblock = compound_types[y.type] + elif y.type in enum_types: + subblock = enum_types[y.type] + elif y.type in flag_types: + subblock = flag_types[y.type] + + # check for links + if action in [ACTION_FIXLINKS, ACTION_GETREFS, ACTION_GETPTRS]: + if not subblock.has_links and not subblock.has_crossrefs: + continue # contains no links, so skip this member! + if action == ACTION_OUT: + if y.is_duplicate: + continue # don't write variables twice + # resolve array & cond references + y_arr1_lmember = None + y_arr2_lmember = None + y_cond_lmember = None + y_arg = None + y_arr1_prefix = "" + y_arr2_prefix = "" + y_cond_prefix = "" + y_arg_prefix = "" + if y.arr1.lhs or y.arr2.lhs or y.cond.lhs or y.arg: + for z in block.members: + if not y_arr1_lmember and y.arr1.lhs == z.name: + y_arr1_lmember = z + if not y_arr2_lmember and y.arr2.lhs == z.name: + y_arr2_lmember = z + if not y_cond_lmember: + if y.cond.lhs == z.name: + y_cond_lmember = z + elif y.cond.op == '&&' and y.cond.lhs == z.name: + y_cond_lmember = z + elif y.cond.op == '||' and y.cond.lhs == z.name: + y_cond_lmember = z + if not y_arg and y.arg == z.name: + y_arg = z + if y_arr1_lmember: + y_arr1_prefix = prefix + if y_arr2_lmember: + y_arr2_prefix = prefix + if y_cond_lmember: + y_cond_prefix = prefix + if y_arg: + y_arg_prefix = prefix + # resolve this prefix + y_prefix = prefix + # resolve arguments + if y.arr1 and y.arr1.lhs == 'ARG': + y.arr1.lhs = arg_member.name + y.arr1.clhs = arg_member.cname + y_arr1_prefix = arg_prefix + if y.arr2 and y.arr2.lhs == 'ARG': + y.arr2.lhs = arg_member.name + y.arr2.clhs = arg_member.cname + y_arr2_prefix = arg_prefix + if y.cond and y.cond.lhs == 'ARG': + y.cond.lhs = arg_member.name + y.cond.clhs = arg_member.cname + y_cond_prefix = arg_prefix + # conditioning + y_cond = y.cond.code(y_cond_prefix) + y_vercond = y.vercond.code('info.') + if action in [ACTION_READ, ACTION_WRITE, ACTION_FIXLINKS]: + if lastver1 != y.ver1 or lastver2 != y.ver2 or lastuserver != y.userver or lastvercond != y_vercond: + # we must switch to a new version block + # close old version block + if lastver1 or lastver2 or lastuserver or lastvercond: self.code("};") + # close old condition block as well + if lastcond: + self.code("};") + lastcond = None + # start new version block + + concat = '' + verexpr = '' + if y.ver1: + verexpr = "( info.version >= 0x%08X )"%y.ver1 + concat = " && " + if y.ver2: + verexpr = "%s%s( info.version <= 0x%08X )"%(verexpr, concat, y.ver2) + concat = " && " + if y.userver != None: + verexpr = "%s%s( info.userVersion == %s )"%(verexpr, concat, y.userver) + concat = " && " + if y_vercond: + verexpr = "%s%s( %s )"%(verexpr, concat, y_vercond) + if verexpr: + # remove outer redundant parenthesis + bleft, bright = scanBrackets(verexpr) + if bleft == 0 and bright == (len(verexpr) - 1): + self.code("if %s {"%verexpr) + else: + self.code("if ( %s ) {"%verexpr) + + # start new condition block + if lastcond != y_cond and y_cond: + self.code("if ( %s ) {"%y_cond) + else: + # we remain in the same version block + # check condition block + if lastcond != y_cond: + if lastcond: + self.code("};") + if y_cond: + self.code("if ( %s ) {"%y_cond) + elif action == ACTION_OUT: + # check condition block + if lastcond != y_cond: + if lastcond: + self.code("};") + if y_cond: + self.code("if ( %s ) {"%y_cond) + + # loop over arrays + # and resolve variable name + if not y.arr1.lhs: + z = "%s%s"%(y_prefix, y.cname) + else: + if action == ACTION_OUT: + self.code("array_output_count = 0;") + if y.arr1.lhs.isdigit() == False: + if action == ACTION_READ: + # default to local variable, check if variable is in current scope if not then try to use + # definition from resized child + memcode = "%s%s.resize(%s);"%(y_prefix, y.cname, y.arr1.code(y_arr1_prefix)) + mem = block.find_member(y.arr1.lhs, True) # find member in self or parents + self.code(memcode) + + self.code(\ + "for (unsigned int i%i = 0; i%i < %s%s.size(); i%i++) {"%(self.indent, self.indent, y_prefix, y.cname, self.indent)) + else: + self.code(\ + "for (unsigned int i%i = 0; i%i < %s; i%i++) {"\ + %(self.indent, self.indent, y.arr1.code(y_arr1_prefix), self.indent)) + if action == ACTION_OUT: + self.code('if ( !verbose && ( array_output_count > MAXARRAYDUMP ) ) {') + self.code('%s << "" << endl;'%stream) + self.code('break;') + self.code('};') + + if not y.arr2.lhs: + z = "%s%s[i%i]"%(y_prefix, y.cname, self.indent-1) + else: + if not y.arr2_dynamic: + if y.arr2.lhs.isdigit() == False: + if action == ACTION_READ: + self.code("%s%s[i%i].resize(%s);"%(y_prefix, y.cname, self.indent-1, y.arr2.code(y_arr2_prefix))) + self.code(\ + "for (unsigned int i%i = 0; i%i < %s%s[i%i].size(); i%i++) {"\ + %(self.indent, self.indent, y_prefix, y.cname, self.indent-1, self.indent)) + else: + self.code(\ + "for (unsigned int i%i = 0; i%i < %s; i%i++) {"\ + %(self.indent, self.indent, y.arr2.code(y_arr2_prefix), self.indent)) + else: + if action == ACTION_READ: + self.code("%s%s[i%i].resize(%s[i%i]);"%(y_prefix, y.cname, self.indent-1, y.arr2.code(y_arr2_prefix), self.indent-1)) + self.code(\ + "for (unsigned int i%i = 0; i%i < %s[i%i]; i%i++) {"\ + %(self.indent, self.indent, y.arr2.code(y_arr2_prefix), self.indent-1, self.indent)) + z = "%s%s[i%i][i%i]"%(y_prefix, y.cname, self.indent-2, self.indent-1) + + if native_types.has_key(y.type): + # these actions distinguish between refs and non-refs + if action in [ACTION_READ, ACTION_WRITE, ACTION_FIXLINKS, ACTION_GETREFS, ACTION_GETPTRS]: + if (not subblock.is_link) and (not subblock.is_crossref): + # not a ref + if action in [ACTION_READ, ACTION_WRITE]: + # hack required for vector + if y.type == "bool" and y.arr1.lhs: + self.code("{"); + if action == ACTION_READ: + self.code("bool tmp;") + self.code("NifStream( tmp, %s, info );"%(stream)) + self.code("%s = tmp;" % z) + else: # ACTION_WRITE + self.code("bool tmp = %s;" % z) + self.code("NifStream( tmp, %s, info );"%(stream)) + self.code("};") + # the usual thing + elif not y.arg: + self.code("NifStream( %s, %s, info );"%(z, stream)) + else: + self.code("NifStream( %s, %s, info, %s%s );"%(z, stream, y_prefix, y.carg)) + else: + # a ref + if action == ACTION_READ: + self.code("NifStream( block_num, %s, info );"%stream) + self.code("link_stack.push_back( block_num );") + elif action == ACTION_WRITE: + self.code("if ( info.version < VER_3_3_0_13 ) {") + self.code("WritePtr32( &(*%s), %s );"%(z, stream)) + self.code("} else {") + self.code("if ( %s != NULL ) {"%z) + self.code("map::const_iterator it = link_map.find( StaticCast(%s) );" % z) + self.code("if (it != link_map.end()) {") + self.code("NifStream( it->second, %s, info );"%stream) + self.code("missing_link_stack.push_back( NULL );") + self.code("} else {") + self.code("NifStream( 0xFFFFFFFF, %s, info );"%stream) + self.code("missing_link_stack.push_back( %s );" %z) + self.code("}") + self.code("} else {") + self.code("NifStream( 0xFFFFFFFF, %s, info );"%stream) + self.code("missing_link_stack.push_back( NULL );") + self.code("}") + self.code("}") + elif action == ACTION_FIXLINKS: + self.code("%s = FixLink<%s>( objects, link_stack, missing_link_stack, info );"%(z,y.ctemplate)) + + elif action == ACTION_GETREFS and subblock.is_link: + if not y.is_duplicate: + self.code('if ( %s != NULL )\n\trefs.push_back(StaticCast(%s));'%(z,z)) + elif action == ACTION_GETPTRS and subblock.is_crossref: + if not y.is_duplicate: + self.code('if ( %s != NULL )\n\tptrs.push_back((NiObject *)(%s));'%(z,z)) + # the following actions don't distinguish between refs and non-refs + elif action == ACTION_OUT: + if not y.arr1.lhs: + self.code('%s << "%*s%s: " << %s << endl;'%(stream, 2*self.indent, "", y.name, z)) + else: + self.code('if ( !verbose && ( array_output_count > MAXARRAYDUMP ) ) {') + self.code('break;') + self.code('};') + self.code('%s << "%*s%s[" << i%i << "]: " << %s << endl;'%(stream, 2*self.indent, "", y.name, self.indent-1, z)) + self.code('array_output_count++;') + else: + subblock = compound_types[y.type] + if not y.arr1.lhs: + self.stream(subblock, action, "%s%s_"%(localprefix, y.cname), "%s."%z, y_arg_prefix, y_arg) + elif not y.arr2.lhs: + self.stream(subblock, action, "%s%s_"%(localprefix, y.cname), "%s."%z, y_arg_prefix, y_arg) + else: + self.stream(subblock, action, "%s%s_"%(localprefix, y.cname), "%s."%z, y_arg_prefix, y_arg) + + # close array loops + if y.arr1.lhs: + self.code("};") + if y.arr2.lhs: + self.code("};") + + lastver1 = y.ver1 + lastver2 = y.ver2 + lastuserver = y.userver + lastcond = y_cond + lastvercond = y_vercond + + if action in [ACTION_READ, ACTION_WRITE, ACTION_FIXLINKS]: + if lastver1 or lastver2 or not(lastuserver is None) or lastvercond: + self.code("};") + if action in [ACTION_READ, ACTION_WRITE, ACTION_FIXLINKS, ACTION_OUT]: + if lastcond: + self.code("};") + + # the end + if isinstance(block, Block) or block.name in ["Header", "Footer"]: + if action == ACTION_OUT: + self.code("return out.str();") + if action == ACTION_GETREFS: + self.code("return refs;") + if action == ACTION_GETPTRS: + self.code("return ptrs;") + + # declaration + # print "$t Get$n() const; \nvoid Set$n($t value);\n\n"; + def getset_declare(self, block, prefix = ""): # prefix is used to tag local variables only + for y in block.members: + if not y.func: + if y.cname.lower().find("unk") == -1: + self.code( y.getter_declare("", ";") ) + self.code( y.setter_declare("", ";") ) + self.code() + + +def class_name(n): + """ + Formats a valid C++ class name from the name format used in the XML. + @param n: The class name to format in C++ style. + @type n: string + @return The resulting valid C++ class name + @rtype: string + """ + if n == None: return None + try: + return native_types[n] + except KeyError: + return n.replace(' ', '_') + + if n == None: return None + try: + return native_types[n] + except KeyError: + pass + if n == 'TEMPLATE': return 'T' + n2 = '' + for i, c in enumerate(n): + if ('A' <= c) and (c <= 'Z'): + if i > 0: n2 += '_' + n2 += c.lower() + elif (('a' <= c) and (c <= 'z')) or (('0' <= c) and (c <= '9')): + n2 += c + else: + n2 += '_' + return n2 + +def define_name(n): + """ + Formats an all-uppercase version of the name for use in C++ defines. + @param n: The class name to format in define style. + @type n: string + @return The resulting valid C++ define name + @rtype: string + """ + n2 = '' + for i, c in enumerate(n): + if ('A' <= c) and (c <= 'Z'): + if i > 0: + n2 += '_' + n2 += c + else: + n2 += c + elif (('a' <= c) and (c <= 'z')) or (('0' <= c) and (c <= '9')): + n2 += c.upper() + else: + n2 += '_' + return n2 + +def member_name(n): + """ + Formats a version of the name for use as a C++ member variable. + @param n: The attribute name to format in variable style. + @type n: string + @return The resulting valid C++ variable name + @rtype: string + """ + if n == None: return None + if n == 'ARG': return 'ARG' + n2 = '' + lower = True + for i, c in enumerate(n): + if c == ' ': + lower = False + elif (('A' <= c) and (c <= 'Z')) or (('a' <= c) and (c <= 'z')) or (('0' <= c) and (c <= '9')): + if lower: + n2 += c.lower() + else: + n2 += c.upper() + lower = True + else: + n2 += '_' + lower = True + return n2 + +def version2number(s): + """ + Translates a legible NIF version number to the packed-byte numeric representation. For example, "10.0.1.0" is translated to 0x0A000100. + @param s: The version string to translate into numeric form. + @type s: string + @return The resulting numeric version of the given version string. + @rtype: int + """ + if not s: return None + l = s.split('.') + if len(l) > 4: + assert(False) + return int(s) + + if len(l) == 2: + version = 0 + version += int(l[0]) << (3 * 8) + if len(l[1]) >= 1: + version += int(l[1][0]) << (2 * 8) + if len(l[1]) >= 2: + version += int(l[1][1]) << (1 * 8) + if len(l[1]) >= 3: + version += int(l[1][2:]) + return version + else: + version = 0 + for i in range( 0, len(l) ): + version += int(l[i]) << ((3-i) * 8) + #return (int(l[0]) << 24) + (int(l[1]) << 16) + (int(l[2]) << 8) + int(l[3]) + return version + + +def userversion2number(s): + """ + Translates a legible NIF user version number to the packed-byte numeric representation. + Currently just converts the string to an int as this may be a raw number. + Probably to be used just in case this understanding changes. + @param s: The version string to translate into numeric form. + @type s: string + @return The resulting numeric version of the given version string. + @rtype: int + """ + if not s: return None + return int(s) + +def scanBrackets(expr_str, fromIndex = 0): + """Looks for matching brackets. + + >>> scanBrackets('abcde') + (-1, -1) + >>> scanBrackets('()') + (0, 1) + >>> scanBrackets('(abc(def))g') + (0, 9) + >>> s = ' (abc(dd efy 442))xxg' + >>> startpos, endpos = scanBrackets(s) + >>> print s[startpos+1:endpos] + abc(dd efy 442) + """ + startpos = -1 + endpos = -1 + scandepth = 0 + for scanpos in xrange(fromIndex, len(expr_str)): + scanchar = expr_str[scanpos] + if scanchar == "(": + if startpos == -1: + startpos = scanpos + scandepth += 1 + elif scanchar == ")": + scandepth -= 1 + if scandepth == 0: + endpos = scanpos + break + else: + if startpos != -1 or endpos != -1: + raise ValueError("expression syntax error (non-matching brackets?)") + return (startpos, endpos) + +class Expression(object): + """This class represents an expression. + + >>> class A(object): + ... x = False + ... y = True + >>> a = A() + >>> e = Expression('x || y') + >>> e.eval(a) + 1 + >>> Expression('99 & 15').eval(a) + 3 + >>> bool(Expression('(99&15)&&y').eval(a)) + True + >>> a.hello_world = False + >>> def nameFilter(s): + ... return 'hello_' + s.lower() + >>> bool(Expression('(99 &15) &&WoRlD', name_filter = nameFilter).eval(a)) + False + >>> Expression('c && d').eval(a) + Traceback (most recent call last): + ... + AttributeError: 'A' object has no attribute 'c' + >>> bool(Expression('1 == 1').eval()) + True + >>> bool(Expression('1 != 1').eval()) + False + """ + operators = [ '==', '!=', '>=', '<=', '&&', '||', '&', '|', '-', '+', '>', '<' ] + def __init__(self, expr_str, name_filter = None): + self._code = expr_str + left, self._op, right = self._partition(expr_str) + self._left = self._parse(left, name_filter) + if right: + self._right = self._parse(right, name_filter) + else: + self._right = '' + + def eval(self, data = None): + """Evaluate the expression to an integer.""" + + if isinstance(self._left, Expression): + left = self._left.eval(data) + elif isinstance(self._left, basestring): + left = getattr(data, self._left) if self._left != '""' else "" + else: + assert(isinstance(self._left, (int, long))) # debug + left = self._left + + if not self._op: + return left + + if isinstance(self._right, Expression): + right = self._right.eval(data) + elif isinstance(self._right, basestring): + right = getattr(data, self._right) if self._right != '""' else "" + else: + assert(isinstance(self._right, (int, long))) # debug + right = self._right + + if self._op == '==': + return int(left == right) + elif self._op == '!=': + return int(left != right) + elif self._op == '>=': + return int(left >= right) + elif self._op == '<=': + return int(left <= right) + elif self._op == '&&': + return int(left and right) + elif self._op == '||': + return int(left or right) + elif self._op == '&': + return left & right + elif self._op == '|': + return left | right + elif self._op == '-': + return left - right + elif self._op == '+': + return left + right + elif self._op == '!': + return not left + else: + raise NotImplementedError("expression syntax error: operator '" + op + "' not implemented") + + def __str__(self): + """Reconstruct the expression to a string.""" + + left = str(self._left) + if not self._op: return left + right = str(self._right) + return left + ' ' + self._op + ' ' + right + + @classmethod + def _parse(cls, expr_str, name_filter = None): + """Returns an Expression, string, or int, depending on the + contents of .""" + # brackets or operators => expression + if ("(" in expr_str) or (")" in expr_str): + return Expression(expr_str, name_filter) + for op in cls.operators: + if expr_str.find(op) != -1: + return Expression(expr_str, name_filter) + + mver = re.compile("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+") + iver = re.compile("[0-9]+") + # try to convert it to an integer + try: + if mver.match(expr_str): + return "0x%08X"%(version2number(expr_str)) + elif iver.match(expr_str): + return str(int(expr_str)) + except ValueError: + pass + # failed, so return the string, passed through the name filter + return name_filter(expr_str) if name_filter else expr_str + + @classmethod + def _partition(cls, expr_str): + """Partitions expr_str. See examples below. + + >>> Expression._partition('abc || efg') + ('abc', '||', 'efg') + >>> Expression._partition('abc||efg') + ('abc', '||', 'efg') + >>> Expression._partition('abcdefg') + ('abcdefg', '', '') + >>> Expression._partition(' abcdefg ') + ('abcdefg', '', '') + >>> Expression._partition(' (a | b) & c ') + ('a | b', '&', 'c') + >>> Expression._partition('(a | b)!=(b&c)') + ('a | b', '!=', 'b&c') + >>> Expression._partition('(a== b) &&(( b!=c)||d )') + ('a== b', '&&', '( b!=c)||d') + """ + # check for unary operators + if expr_str.strip().startswith('!'): + return expr_str.lstrip(' !'), '!', None + lenstr = len(expr_str) + # check if the left hand side starts with brackets + # and if so, find the position of the starting bracket and the ending + # bracket + left_startpos, left_endpos = cls._scanBrackets(expr_str) + if left_startpos >= 0: + # yes, it is a bracketted expression + # so remove brackets and whitespace, + # and let that be the left hand side + left_str = expr_str[left_startpos+1:left_endpos].strip() + + # the next token should be the operator + # find the position where the operator should start + op_startpos = left_endpos+1 + while op_startpos < lenstr and expr_str[op_startpos] == " ": + op_startpos += 1 + if op_startpos < lenstr: + # to avoid confusion between && and &, and || and |, + # let's first scan for operators of two characters + # and then for operators of one character + for op_endpos in xrange(op_startpos+1, op_startpos-1, -1): + op_str = expr_str[op_startpos:op_endpos+1] + if op_str in cls.operators: + break + else: + raise ValueError("expression syntax error: expected operator at '%s'"%expr_str[op_startpos:]) + else: + return cls._partition(left_str) + else: + # it's not... so we need to scan for the first operator + for op_startpos, ch in enumerate(expr_str): + if ch == ' ': continue + if ch == '(' or ch == ')': + raise ValueError("expression syntax error: expected operator before '%s'"%expr_str[op_startpos:]) + # to avoid confusion between && and &, and || and |, + # let's first scan for operators of two characters + for op_endpos in xrange(op_startpos+1, op_startpos-1, -1): + op_str = expr_str[op_startpos:op_endpos+1] + if op_str in cls.operators: + break + else: + continue + break + else: + # no operator found, so we are done + left_str = expr_str.strip() + op_str = '' + right_str = '' + return left_str, op_str, right_str + # operator found! now get the left hand side + left_str = expr_str[:op_startpos].strip() + + return left_str, op_str, expr_str[op_endpos+1:].strip() + + @staticmethod + def _scanBrackets(expr_str, fromIndex = 0): + """Looks for matching brackets. + + >>> Expression._scanBrackets('abcde') + (-1, -1) + >>> Expression._scanBrackets('()') + (0, 1) + >>> Expression._scanBrackets('(abc(def))g') + (0, 9) + >>> s = ' (abc(dd efy 442))xxg' + >>> startpos, endpos = Expression._scanBrackets(s) + >>> print s[startpos+1:endpos] + abc(dd efy 442) + """ + startpos = -1 + endpos = -1 + scandepth = 0 + for scanpos in xrange(fromIndex, len(expr_str)): + scanchar = expr_str[scanpos] + if scanchar == "(": + if startpos == -1: + startpos = scanpos + scandepth += 1 + elif scanchar == ")": + scandepth -= 1 + if scandepth == 0: + endpos = scanpos + break + else: + if startpos != -1 or endpos != -1: + raise ValueError("expression syntax error (non-matching brackets?)") + return (startpos, endpos) + + def code(self, prefix = '', brackets = True, name_filter = None): + """Format an expression as a string. + @param prefix: An optional prefix. + @type prefix: string + @param brackets: If C{True}, then put expression between brackets. + @type prefix: string + @return The expression formatted into a string. + @rtype: string + """ + lbracket = "(" if brackets else "" + rbracket = ")" if brackets else "" + if not self._op: + if not self.lhs: return '' + if isinstance(self.lhs, types.IntType): + return self.lhs + elif self.lhs in block_types: + return 'IsDerivedType(%s::TYPE)' % self.lhs + else: + return prefix + (name_filter(self.lhs) if name_filter else self.lhs) + elif self._op == '!': + lhs = self.lhs + if isinstance(lhs, Expression): + lhs = lhs.code(prefix, True, name_filter) + elif lhs in block_types: + lhs = 'IsDerivedType(%s::TYPE)' % lhs + elif lhs and not lhs.isdigit() and not lhs.startswith('0x'): + lhs = prefix + (name_filter(lhs) if name_filter else lhs) + return '%s%s%s%s'%(lbracket, self._op, lhs, rbracket) + else: + lhs = self.lhs + rhs = self.rhs + if isinstance(lhs, Expression): + lhs = lhs.code(prefix, True, name_filter) + elif lhs in block_types: + lhs = 'IsDerivedType(%s::TYPE)' % lhs + elif lhs and not lhs.isdigit() and not lhs.startswith('0x'): + lhs = prefix + (name_filter(lhs) if name_filter else lhs) + if isinstance(rhs, Expression): + rhs = rhs.code(prefix, True, name_filter) + elif rhs in block_types: + rhs = 'IsDerivedType(%s::TYPE)' % rhs + elif rhs and not rhs.isdigit() and not rhs.startswith('0x'): + rhs = prefix + (name_filter(rhs) if name_filter else rhs) + return '%s%s %s %s%s'%(lbracket, lhs, self._op, rhs, rbracket) + + def get_terminals(self): + """Return all terminal names (without operators or brackets).""" + if isinstance(self.lhs, Expression): + for terminal in self.lhs.get_terminals(): + yield terminal + elif self.lhs: + yield self.lhs + if isinstance(self.rhs, Expression): + for terminal in self.rhs.get_terminals(): + yield terminal + elif self.rhs: + yield self.rhs + + def __getattr__(self, name): + if (name == 'lhs'): + return getattr(self, '_left') + if (name == 'rhs'): + return getattr(self, '_right') + if (name == 'op'): + return getattr(self, '_op') + return object.__getattribute__(self, name) + + # ducktyping: pretend we're also a string with isdigit() method + def isdigit(self): + return False + +class Expr(Expression): + """ + Represents a mathmatical expression? + @ivar lhs: The left hand side of the expression? + @type lhs: string + @ivar clhs: The C++ formatted version of the left hand side of the expression? + @type clhs: string + @ivar op: The operator used in the expression. ==, &&, !=, etc. + @type op: string + @ivar rhs: The right hand side of the expression? + @type rhs: string + """ + def __init__(self, n, name_filter = None): + """ + This constructor takes the expression in the form of a string and tokenizes it into left-hand side, operator, right hand side, and something called clhs. + @param n: The expression to tokenize. + @type n: string + """ + Expression.__init__(self, n, name_filter) + + def code(self, prefix = '', brackets = True, name_filter = None): + if not name_filter: + name_filter = member_name + return Expression.code(self, prefix, brackets, name_filter) + +class Option: + """ + This class represents an option in an option list. + @ivar value: The C++ value of option variable. Comes from the "value" attribute of the . + @type description: string + @ivar cname: The name of this member for use in C++. + @type cname: string + """ + def __init__(self, element): + """ + This constructor converts an XML