Importing svn2cl as it is used from script that makes release archives.

Making code DBus optional; all DBus related code is wrapped inside EDELIB_HAVE_DBUS.
This commit is contained in:
Sanel Zukan 2012-09-18 17:49:48 +00:00
parent 0ad6617df4
commit c26b526dcd
12 changed files with 1004 additions and 50 deletions

View File

@ -106,10 +106,7 @@ else
fi
if test -n "$with_edelib_path"; then
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$with_edelib_path"
else
dnl TODO: remove this in release
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/opt/ede/lib/pkgconfig"
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$with_edelib_path/lib/pkgconfig"
fi
PKG_CHECK_MODULES(EDELIB, [edelib],, [have_edelib=no])
@ -129,7 +126,7 @@ fi
PKG_CHECK_MODULES(EDELIB_DBUS, [edelib-dbus],, [have_edelib_dbus=no])
if test "$have_edelib_dbus" = "no"; then
AC_MSG_ERROR(edelib-dbus not found! You must install it first)
AC_MSG_RESULT(edelib is compiled without DBus so EDE will be compiled without DBus support)
fi
dnl find edelib documentation

View File

@ -224,7 +224,10 @@ Desktop::~Desktop() {
delete gisett;
delete selbox;
#ifdef EDELIB_HAVE_DBUS
delete dbus;
#endif
DirWatch::shutdown();
foreign_callback_remove(settings_changed_cb);
@ -244,12 +247,14 @@ void Desktop::init_internals(void) {
wallpaper = new Wallpaper(0, 0, w(), h());
end();
#ifdef EDELIB_HAVE_DBUS
dbus = new edelib::EdbusConnection();
if(!dbus->connect(edelib::EDBUS_SESSION)) {
E_WARNING(E_STRLOC ": Unable to connect to session bus. Disabling dbus interface...\n");
delete dbus;
dbus = NULL;
}
#endif
/* read main config */
read_config();
@ -820,6 +825,7 @@ void Desktop::notify_desktop_changed(void) {
return;
}
#ifdef EDELIB_HAVE_DBUS
if(dbus) {
edelib::EdbusMessage msg;
/* send org.equinoxproject.Desktop.DesktopChanged(int32, string) signal */
@ -827,6 +833,7 @@ void Desktop::notify_desktop_changed(void) {
msg << num << names[num];
dbus->send(msg);
}
#endif
XFreeStringList(names);
}

View File

@ -27,9 +27,12 @@
#include <edelib/String.h>
#include <edelib/Resource.h>
#include <edelib/List.h>
#include <edelib/EdbusConnection.h>
#include <edelib/MenuButton.h>
#ifdef EDELIB_HAVE_DBUS
# include <edelib/EdbusConnection.h>
#endif
#define EDAMAGE_CHILD_LABEL 0x10
#define EDAMAGE_OVERLAY 0x20
@ -109,7 +112,10 @@ private:
edelib::MenuButton* dmenu;
Wallpaper* wallpaper;
#ifdef EDELIB_HAVE_DBUS
edelib::EdbusConnection* dbus;
#endif
DesktopIconList icons;
DesktopIconList selectionbuf;

View File

@ -1,13 +1,21 @@
/*
* $Id$
*
* ede-launch, launch external application
* Part of Equinox Desktop Environment (EDE).
* Copyright (c) 2008-2009 EDE Authors.
*
* This program is licensed under terms of the
* GNU General Public License version 2 or newer.
* See COPYING for details.
* Copyright (C) 2012 Sanel Zukan
*
* 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.
*/
#ifdef HAVE_CONFIG_H
@ -47,6 +55,7 @@
#include <edelib/StrUtil.h>
#include <edelib/Debug.h>
#include <edelib/Regex.h>
#include <edelib/Util.h>
#include <edelib/Ede.h>
#include "StartupNotify.h"
@ -63,13 +72,14 @@
#define REGEX_PATTERN_URL "((http|https|ftp|gopher|!file):\\/\\/|www)[a-zA-Z0-9\\-\\._]+\\/?[a-zA-Z0-9_\\.\\-\\?\\+\\/~=&#;,]*[a-zA-Z0-9\\/]{1}"
EDELIB_NS_USING_AS(Window, LaunchWindow)
EDELIB_NS_USING_LIST(12, (Resource,
EDELIB_NS_USING_LIST(14, (Resource,
Regex,
String,
list,
DesktopFile,
RES_USER_ONLY,
DESK_FILE_TYPE_APPLICATION,
run_sync, run_async, alert, file_path, window_center_on_screen, str_ends))
run_sync, run_async, alert, file_path, window_center_on_screen, str_ends, system_data_dirs))
static Fl_Pixmap image_run((const char**)run_xpm);
static Fl_Input* dialog_input;
@ -348,6 +358,17 @@ FAIL:
return false;
}
static bool start_via_mime_cache(const char *arg) {
list<String> dirs;
E_RETURN_VAL_IF_FAIL(system_data_dirs(dirs) > 0, false);
DesktopFile d;
list<String>::iterator it = dirs.begin(), ite = dirs.end();
for(; it != ite; ++it) {
}
}
/* concat all arguments preparing it for start_child() */
static void join_args(int start, int argc, char **argv, const char *program, String &ret, bool is_mailto = false) {
String args;

View File

@ -26,11 +26,13 @@
#include <string.h>
#include <limits.h>
#include <edelib/Ede.h>
#include <edelib/Debug.h>
#ifdef EDELIB_HAVE_DBUS
#include <FL/Fl.H>
#include <FL/Fl_Shared_Image.H>
#include <edelib/Ede.h>
#include <edelib/Debug.h>
#include <edelib/EdbusConnection.h>
#include <edelib/EdbusMessage.h>
#include <edelib/EdbusData.h>
@ -63,7 +65,6 @@
#define WINDOWS_PADDING 10
#define IS_MEMBER(m, s1) (strcmp((m->member()), (s1)) == 0)
#define CHECK_ARGV(argv, pshort, plong) ((strcmp(argv, pshort) == 0) || (strcmp(argv, plong) == 0))
EDELIB_NS_USING(EdbusConnection)
EDELIB_NS_USING(EdbusMessage)
@ -298,6 +299,8 @@ static int notifications_dbus_method_cb(const EdbusMessage *m, void *d) {
return 1;
}
#endif /* EDELIB_HAVE_DBUS */
#if 0
static int notifications_dbus_signal_cb(const EdbusMessage *m, void *d) {
E_DEBUG("+=> %s\n", m->member());
@ -313,6 +316,8 @@ static void help(void) {
puts(" -n, --no-daemon do not run in background");
}
#define CHECK_ARGV(argv, pshort, plong) ((strcmp(argv, pshort) == 0) || (strcmp(argv, plong) == 0))
int main(int argc, char **argv) {
/* daemon behaves as GUI app, as will use icon theme and etc. */
EDE_APPLICATION("ede-notify-daemon");
@ -329,6 +334,7 @@ int main(int argc, char **argv) {
}
}
#ifdef EDELIB_HAVE_DBUS
server_running = false;
notify_id = 0;
EdbusConnection dbus;
@ -359,5 +365,8 @@ int main(int argc, char **argv) {
Fl::wait(FOREVER);
IconLoader::shutdown();
#else
E_WARNING(E_STRLOC ": edelib is compiled without DBus so notification daemon is not able to receive notification messages\n");
#endif
return 0;
}

View File

@ -26,8 +26,11 @@
#include <edelib/MessageBox.h>
#include <edelib/Nls.h>
#include <edelib/Run.h>
#if EDELIB_HAVE_DBUS
#include <edelib/EdbusMessage.h>
#include <edelib/EdbusConnection.h>
#endif
#include "EvokeService.h"
#include "Splash.h"
@ -37,11 +40,6 @@
EDELIB_NS_USING(Config)
EDELIB_NS_USING(Resource)
EDELIB_NS_USING(EdbusMessage)
EDELIB_NS_USING(EdbusConnection)
EDELIB_NS_USING(EdbusError)
EDELIB_NS_USING(EDBUS_SESSION)
EDELIB_NS_USING(EDBUS_SYSTEM)
EDELIB_NS_USING(RES_SYS_ONLY)
EDELIB_NS_USING(file_remove)
EDELIB_NS_USING(file_test)
@ -51,6 +49,14 @@ EDELIB_NS_USING(alert)
EDELIB_NS_USING(ask)
EDELIB_NS_USING(FILE_TEST_IS_REGULAR)
#if EDELIB_HAVE_DBUS
EDELIB_NS_USING(EdbusMessage)
EDELIB_NS_USING(EdbusConnection)
EDELIB_NS_USING(EdbusError)
EDELIB_NS_USING(EDBUS_SESSION)
EDELIB_NS_USING(EDBUS_SYSTEM)
#endif
#ifdef USE_LOCAL_CONFIG
# define CONFIG_GET_STRVAL(object, section, key, buff) object.get(section, key, buff, sizeof(buff))
#else
@ -90,17 +96,21 @@ static int get_int_property_value(Atom at) {
}
static void send_dbus_ede_quit(void) {
#ifdef EDELIB_HAVE_DBUS
EdbusConnection c;
E_RETURN_IF_FAIL(c.connect(EDBUS_SESSION));
EdbusMessage msg;
msg.create_signal("/org/equinoxproject/Shutdown", "org.equinoxproject.Shutdown", "Shutdown");
c.send(msg);
#endif
}
static bool do_shutdown_or_restart(bool restart) {
const char *action;
int r = 1;
#ifdef EDELIB_HAVE_DBUS
EdbusConnection c;
if(!c.connect(EDBUS_SYSTEM)) {
alert(_("Unable to connect to HAL daemon. Make sure both D-BUS and HAL daemons are running"));
@ -131,8 +141,9 @@ static bool do_shutdown_or_restart(bool restart) {
if((*it).to_bool() == true)
return true;
int r = ask(_("You are not allowed to execute this command. Please consult ConsoleKit documentation on how to allow privileged actions. "
"Would you like to try to execute system commands?"));
r = ask(_("You are not allowed to execute this command. Please consult ConsoleKit documentation on how to allow privileged actions. "
"Would you like to try to execute system commands?"));
#endif /* EDELIB_HAVE_DBUS */
/* try to do things manually */
if(!r) return false;

View File

@ -26,7 +26,10 @@
#include <edelib/File.h>
#include <edelib/Resource.h>
#include <edelib/Nls.h>
#include <edelib/EdbusList.h>
#ifdef EDELIB_HAVE_DBUS
# include <edelib/EdbusList.h>
#endif
#include "Xsm.h"
@ -41,9 +44,12 @@ EDELIB_NS_USING(Resource)
EDELIB_NS_USING(XSettingsSetting)
EDELIB_NS_USING(XSettingsList)
#ifdef EDELIB_HAVE_DBUS
EDELIB_NS_USING(EdbusMessage)
EDELIB_NS_USING(EdbusData)
EDELIB_NS_USING(EdbusList)
EDELIB_NS_USING(EDBUS_SESSION)
#endif
EDELIB_NS_USING(dir_home)
EDELIB_NS_USING(file_remove)
@ -58,7 +64,8 @@ EDELIB_NS_USING(color_fltk_to_html)
EDELIB_NS_USING(XSETTINGS_TYPE_COLOR)
EDELIB_NS_USING(XSETTINGS_TYPE_INT)
EDELIB_NS_USING(XSETTINGS_TYPE_STRING)
EDELIB_NS_USING(EDBUS_SESSION)
#define STR_CMP(s1, s2) (strcmp((s1), (s2)) == 0)
struct ResourceMap {
const char* name;
@ -83,6 +90,7 @@ static int ignore_xerrors(Display* display, XErrorEvent* xev) {
return True;
}
#ifdef EDELIB_HAVE_DBUS
static void handle_get_type(XSettingsData* mdata, const EdbusMessage* orig, EdbusMessage& reply) {
if(orig->size() != 1) {
reply.create_error_reply(*orig, _("This function accepts only one parameter"));
@ -247,13 +255,75 @@ static void handle_set(Xsm* xsm, XSettingsData* mdata, const EdbusMessage* orig,
}
}
#define XSM_OBJECT_PATH "/org/equinoxproject/Xsettings"
#define XSM_INTROSPECTION_XML \
"<node>\n"\
" <interface name=\"org.equinoxproject.Xsettings\">\n" \
" <method name=\"GetType\">\n" \
" <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"type\" type=\"s\" direction=\"out\"/>\n" \
" <annotation name=\"org.equinoxproject.DBus.DocString\" value=\"Returns type for given name.\" />\n" \
" </method>\n" \
" <method name=\"GetAll\">\n" \
" <arg name=\"all\" type=\"as\" direction=\"out\"/>\n" \
" <annotation name=\"org.equinoxproject.DBus.DocString\" value=\"Get all settings currently registered.\" />\n" \
" </method>\n" \
" <method name=\"GetValue\">\n" \
" <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"value\" type=\"v\" direction=\"out\"/>\n" \
" <annotation name=\"org.equinoxproject.DBus.DocString\" value=\"Returns value for named setting.\" />\n" \
" </method>\n" \
" <method name=\"Remove\">\n" \
" <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
" <annotation name=\"org.equinoxproject.DBus.DocString\" value=\"Removes named setting. If not exists, does nothing.\" />\n" \
" </method>\n" \
" <method name=\"Flush\">\n" \
" <annotation name=\"org.equinoxproject.DBus.DocString\" value=\"Flushes all settings to disk.\" />\n" \
" </method>\n" \
" <method name=\"Set\">\n" \
" <arg name=\"status\" type=\"b\" direction=\"out\"/>\n" \
" <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
" <arg name=\"value\" type=\"v\" direction=\"in\"/>\n" \
" <annotation name=\"org.equinoxproject.DBus.DocString\" value=\"Set named setting to given value. Returns true if set or false if failed.\" />\n" \
" </method>\n" \
" </interface>\n" \
"</node>\n";
static int xsettings_dbus_cb(const EdbusMessage* m, void* data) {
Xsm* x = (Xsm*)data;
XSettingsData* md = x->get_manager_data();
/* introspection */
if(STR_CMP(m->member(), "Introspect") &&
STR_CMP(m->interface(), "org.freedesktop.DBus.Introspectable") &&
STR_CMP(m->destination(), "org.equinoxproject.Xsettings"))
{
String ret = EDBUS_INTROSPECTION_DTD;
if(STR_CMP(m->path(), XSM_OBJECT_PATH)) {
ret += XSM_INTROSPECTION_XML;
} else {
ret += "<node>\n <node ";
if(STR_CMP(m->path(), "/"))
ret += "name=\"org\"";
else if(STR_CMP(m->path(), "/org"))
ret += "name=\"equinoxproject\"";
else if(STR_CMP(m->path(), "/org/equinoxproject"))
ret += "name=\"Xsettings\"";
ret += " />\n</node>\n";
}
EdbusMessage reply;
reply.create_reply(*m);
reply << EdbusData::from_string(ret.c_str());
x->get_dbus_connection()->send(reply);
return 1;
}
/* string GetType(string name) */
if(strcmp(m->member(), "GetType") == 0) {
if(STR_CMP(m->member(), "GetType")) {
EdbusMessage reply;
handle_get_type(md, m, reply);
@ -262,7 +332,7 @@ static int xsettings_dbus_cb(const EdbusMessage* m, void* data) {
}
/* string-array GetAll(void) */
if(strcmp(m->member(), "GetAll") == 0) {
if(STR_CMP(m->member(), "GetAll")) {
EdbusMessage reply;
handle_get_all(md, m, reply);
@ -272,7 +342,7 @@ static int xsettings_dbus_cb(const EdbusMessage* m, void* data) {
}
/* [string|array|int32] GetValue(string name) */
if(strcmp(m->member(), "GetValue") == 0) {
if(STR_CMP(m->member(), "GetValue")) {
EdbusMessage reply;
handle_get_value(md, m, reply);
@ -282,19 +352,19 @@ static int xsettings_dbus_cb(const EdbusMessage* m, void* data) {
}
/* void Remove(string name) */
if(strcmp(m->member(), "Remove") == 0) {
if(STR_CMP(m->member(), "Remove")) {
handle_remove(x, md, m);
return 1;
}
/* void Flush(void) */
if(strcmp(m->member(), "Flush") == 0) {
if(STR_CMP(m->member(), "Flush")) {
x->save_serialized();
return 1;
}
/* bool Set(string name, [string|array|int32] value) */
if(strcmp(m->member(), "Set") == 0) {
if(STR_CMP(m->member(), "Set")) {
EdbusMessage reply;
handle_set(x, md, m, reply);
@ -302,13 +372,10 @@ static int xsettings_dbus_cb(const EdbusMessage* m, void* data) {
return 1;
}
return 1;
return 0;
}
Xsm::~Xsm() {
E_DEBUG(E_STRLOC ": Xsm::~Xsm()\n");
delete dbus_conn;
}
#endif /* EDELIB_HAVE_DBUS */
/*
* This is a short explaination how evoke's XSETTINGS part is combined
@ -373,7 +440,7 @@ void Xsm::xresource_replace(void) {
/* check if resource is present */
status = XrmGetResource(db, resource_map[i].xresource_key, resource_map[i].xresource_klass, &type, &xrmv);
if(status && strcmp(type, "String") == 0) {
if(status && STR_CMP(type, "String")) {
E_DEBUG(E_STRLOC ": %s.%s found in database\n",
resource_map[i].xresource_klass, resource_map[i].xresource_key);
}
@ -427,6 +494,7 @@ void Xsm::xresource_undo(void) {
}
void Xsm::xsettings_dbus_serve(void) {
#ifdef EDELIB_HAVE_DBUS
E_RETURN_IF_FAIL(!dbus_conn);
EdbusConnection* d = new EdbusConnection;
@ -443,11 +511,12 @@ void Xsm::xsettings_dbus_serve(void) {
return;
}
d->register_object("/org/equinoxproject/Xsettings");
d->register_object(XSM_OBJECT_PATH);
d->method_callback(xsettings_dbus_cb, this);
d->setup_listener_with_fltk();
dbus_conn = d;
#endif /* EDELIB_HAVE_DBUS */
}
bool Xsm::load_serialized(void) {
@ -481,7 +550,7 @@ bool Xsm::load_serialized(void) {
return false;
for(elem = elem->FirstChildElement(); elem; elem = elem->NextSibling()) {
if(strcmp(elem->Value(), "setting") != 0) {
if(!STR_CMP(elem->Value(), "setting")) {
E_WARNING(E_STRLOC ": Got unknown child in 'ede-setting' %s\n", elem->Value());
continue;
}
@ -498,11 +567,11 @@ bool Xsm::load_serialized(void) {
continue;
}
if(strcmp(type, "int") == 0)
if(STR_CMP(type, "int"))
cmp = 1;
else if(strcmp(type, "string") == 0)
else if(STR_CMP(type, "string"))
cmp = 2;
else if(strcmp(type, "color") == 0)
else if(STR_CMP(type, "color"))
cmp = 3;
else {
E_WARNING(E_STRLOC ": Unknown type %s\n", type);

View File

@ -14,15 +14,20 @@
#define __XSM_H__
#include <edelib/XSettingsManager.h>
#include <edelib/EdbusConnection.h>
#ifdef EDELIB_HAVE_DBUS
# include <edelib/EdbusConnection.h>
EDELIB_NS_USING(EdbusConnection)
#endif
EDELIB_NS_USING(XSettingsData)
/* XSETTINGS manager with serialization capability. Also it will write/undo to xrdb (X Resource database). */
class Xsm : public edelib::XSettingsManager {
private:
#ifdef EDELIB_HAVE_DBUS
EdbusConnection* dbus_conn;
#endif
/* replace XResource values from one from XSETTINGS */
void xresource_replace(void);
@ -33,11 +38,14 @@ private:
/* serve XSETTINGS via D-Bus */
void xsettings_dbus_serve(void);
public:
Xsm() : dbus_conn(NULL) { }
~Xsm();
#ifdef EDELIB_HAVE_DBUS
Xsm() { dbus_conn = NULL; }
~Xsm() { delete dbus_conn; }
/* return loaded D-Bus connection */
EdbusConnection* get_dbus_connection(void) { return dbus_conn; }
#endif
/* access to manager content */
XSettingsData* get_manager_data(void) { return manager_data; }

View File

@ -6,12 +6,21 @@ RELEASE_VERSION="2.0"
############################################
script_dir="$( cd "$( dirname "$0" )" && pwd )"
release_dir="release-$RELEASE_VERSION"
release_dir_full="`pwd`/$release_dir"
dprint() {
echo "* $1"
}
release_dir="release-$RELEASE_VERSION"
release_dir_full="`pwd`/$release_dir"
generate_changelog() {
$script_dir/svn2cl/svn2cl.sh --authors=$script_dir/svn2cl/authors.xml -o $1/ChangeLog $1
}
upload_file() {
scp $1-$2.tar.gz karijes@frs.sourceforge.net:/home/frs/project/ede/$1/$2/$1-$2.tar.gz
}
# prepare location dir
rm -Rf $release_dir
@ -31,13 +40,15 @@ svn copy "$ede_svn_trunk/edelib" "$ede_svn_tags/edelib-$RELEASE_VERSION" -m "Tag
dprint "Tagging ede"
svn copy "$ede_svn_trunk/ede2" "$ede_svn_tags/ede-$RELEASE_VERSION" -m "Tagging ede to $RELEASE_VERSION"
############################################
###########################################
package="edelib-$RELEASE_VERSION"
dprint "Getting edelib..."
svn co "$ede_svn_trunk/edelib" $package
generate_changelog $package
cd $package
find . -name ".svn" -type d | xargs rm -Rf
./autogen.sh
@ -54,6 +65,8 @@ package="ede-$RELEASE_VERSION"
dprint "Getting ede..."
svn co "$ede_svn_trunk/ede2" $package
generate_changelog $package
cd $package
find . -name ".svn" -type d | xargs rm -Rf
./autogen.sh
@ -77,3 +90,7 @@ cd ..
tar -czpvf $package.tar.gz $package
md5sum $package.tar.gz >> checksum
############################################
#upload_file edelib $RELEASE_VERSION
#upload_file ede $RELEASE_VERSION

9
tools/svn2cl/authors.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<authors xmlns:html="http://www.w3.org/1999/xhtml">
<author uid="karijes">
Sanel Zukan &lt;karijes@users.sourceforge.net&gt;
</author>
<author uid="vljubovic">
Vedran Ljubovic &lt;vljubovic@users.sourceforge.net&gt;
</author>
</authors>

343
tools/svn2cl/svn2cl.sh Executable file
View File

@ -0,0 +1,343 @@
#!/bin/sh
# svn2cl.sh - front end shell script for svn2cl.xsl, calls xsltproc
# with the correct parameters
#
# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Arthur de Jong.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. 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.
# 3. The name of the author may not be used to endorse or promote
# products derived from this software without specific prior
# written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
# exit on any failures
set -e
# report unset variables
set -u
# svn2cl version
VERSION="0.13"
# set default parameters
PWD=`pwd`
STRIPPREFIX="AUTOMATICALLY-DETERMINED"
LINELEN=75
GROUPBYDAY="no"
INCLUDEREV="no"
BREAKBEFOREMSG="no"
REPARAGRAPH="no"
SEPARATEDAYLOGS="no"
ACTIONS="no"
CHANGELOG=""
OUTSTYLE="cl"
SVNLOGCMD="svn --verbose --xml log"
SVNINFOCMD="svn --non-interactive info"
AUTHORSFILE=""
IGNORE_MESSAGE_STARTING=""
TITLE="ChangeLog"
REVISION_LINK="#r"
TICKET_LINK=""
TMPFILES=""
AWK="awk"
# do command line checking
prog=`basename "$0"`
while [ $# -gt 0 ]
do
case "$1" in
--strip-prefix)
STRIPPREFIX="$2"
shift 2 || { echo "$prog: option requires an argument -- $1";exit 1; }
;;
--strip-prefix=*)
STRIPPREFIX=`echo "$1" | sed 's/^--[a-z-]*=//'`
shift
;;
--linelen)
LINELEN="$2";
shift 2 || { echo "$prog: option requires an argument -- $1";exit 1; }
;;
--linelen=*)
LINELEN=`echo "$1" | sed 's/^--[a-z-]*=//'`
shift
;;
--group-by-day)
GROUPBYDAY="yes";
shift
;;
--separate-daylogs)
SEPARATEDAYLOGS="yes"
shift
;;
-i|--include-rev)
INCLUDEREV="yes";
shift
;;
-a|--include-actions)
ACTIONS="yes"
shift
;;
--break-before-msg|--breaks-before-msg)
# FIXME: if next argument is numeric use that as a parameter
BREAKBEFOREMSG="yes"
shift
;;
--break-before-msg=*|--breaks-before-msg=*)
BREAKBEFOREMSG=`echo "$1" | sed 's/^--[a-z-]*=//'`
shift
;;
--reparagraph)
REPARAGRAPH="yes"
shift
;;
--title)
TITLE="$2"
shift 2 || { echo "$prog: option requires an argument -- $1";exit 1; }
;;
--title=*)
TITLE=`echo "$1" | sed 's/^--[a-z-]*=//'`
shift
;;
--revision-link)
REVISION_LINK="$2"
shift 2 || { echo "$prog: option requires an argument -- $1";exit 1; }
;;
--revision-link=*)
REVISION_LINK=`echo "$1" | sed 's/^--[a-z-]*=//'`
shift
;;
--ticket-link)
TICKET_LINKK="$2"
shift 2 || { echo "$prog: option requires an argument -- $1";exit 1; }
;;
--ticket-link=*)
TICKET_LINK=`echo "$1" | sed 's/^--[a-z-]*=//'`
shift
;;
--ignore-message-starting)
IGNORE_MESSAGE_STARTING="$2"
shift 2 || { echo "$prog: option requires an argument -- $1";exit 1; }
;;
--ignore-message-starting=*)
IGNORE_MESSAGE_STARTING=`echo "$1" | sed 's/^--[a-z-]*=//'`
shift
;;
-f|--file|-o|--output)
CHANGELOG="$2"
shift 2 || { echo "$prog: option requires an argument -- $1";exit 1; }
;;
--file=*|--output=*)
CHANGELOG=`echo "$1" | sed 's/^--[a-z-]*=//'`
shift
;;
--stdout)
CHANGELOG="-"
shift
;;
--authors)
AUTHORSFILE="$2"
shift 2 || { echo "$prog: option requires an argument -- $1";exit 1; }
;;
--authors=*)
AUTHORSFILE=`echo "$1" | sed 's/^--[a-z-]*=//'`
shift
;;
--html)
OUTSTYLE="html"
shift
;;
-r|--revision|-c|--change|--targets|-l|--limit)
# add these as extra options to the log command (with argument)
arg=`echo "$2" | sed "s/'/'\"'\"'/g"`
SVNLOGCMD="$SVNLOGCMD $1 '$arg'"
shift 2 || { echo "$prog: option requires an argument -- $1";exit 1; }
;;
--revision=*|--change=*|--targets=*|--limit=*)
# these are single argument versions of the above (with argument)
arg=`echo "$1" | sed "s/'/'\"'\"'/g"`
SVNLOGCMD="$SVNLOGCMD '$arg'"
shift
;;
--username|--password|--config-dir|--config-option)
# add these as extra options to the log and info commands (with argument)
arg=`echo "$2" | sed "s/'/'\"'\"'/g"`
SVNLOGCMD="$SVNLOGCMD $1 '$arg'"
SVNINFOCMD="$SVNINFOCMD $1 '$arg'"
shift 2 || { echo "$prog: option requires an argument -- $1";exit 1; }
;;
--username=*|--password=*|--config-dir=*|--config-option=*)
# these are single argument versions of the above (with argument)
arg=`echo "$1" | sed "s/'/'\"'\"'/g"`
SVNLOGCMD="$SVNLOGCMD '$arg'"
SVNINFOCMD="$SVNINFOCMD '$arg'"
shift
;;
-g|--use-merge-history|--stop-on-copy)
# add these as simple options to the log command
SVNLOGCMD="$SVNLOGCMD $1"
shift
;;
--no-auth-cache|--non-interactive|--trust-server-cert)
# add these as simple options to both the log and info commands
SVNLOGCMD="$SVNLOGCMD $1"
SVNINFOCMD="$SVNINFOCMD $1"
shift
;;
-V|--version)
echo "$prog $VERSION";
echo "Written by Arthur de Jong."
echo ""
echo "Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Arthur de Jong."
echo "This is free software; see the source for copying conditions. There is NO"
echo "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
exit 0
;;
-h|--help)
echo "Usage: $prog [OPTION]... [PATH]..."
echo "Generate a ChangeLog from a subversion repository."
echo ""
echo " --strip-prefix=NAME prefix to strip from all entries, defaults path"
echo " inside the repository"
echo " --linelen=NUM maximum length of an output line"
echo " --group-by-day group changelog entries by day"
echo " --separate-daylogs put a blank line between grouped by day entries"
echo " -i, --include-rev include revision numbers"
echo " -a, --include-actions add [ADD], [DEL] and [CPY] tags to files"
echo " --break-before-msg[=NUM] add a line break (or multiple breaks)"
echo " between the paths and the log message"
echo " --reparagraph rewrap lines inside a paragraph"
echo " --title=NAME title used in html file"
echo " --revision-link=NAME link revision numbers in html output"
echo " --ticket-link=NAME change #foo strings to links"
echo " --ignore-message-starting=STRING"
echo " ignore messages starting with the string"
echo " -o, --output=FILE output to FILE instead of ChangeLog"
echo " -f, --file=FILE alias for -o, --output"
echo " --stdout output to stdout instead of ChangeLog"
echo " --authors=FILE file to read for authors"
echo " --html output as html instead of plain text"
echo " -h, --help display this help and exit"
echo " -V, --version output version information and exit"
echo ""
echo "PATH arguments and the following options are passed to the svn log"
echo "command: -r, --revision, -g, --use-merge-history, -c, --change,"
echo "--targets, --stop-on-copy, -l, --username, --password, --no-auth-cache,"
echo "--non-interactive, --trust-server-cert, --config-dir and --config-option"
echo "(see 'svn help log' for more information)."
exit 0
;;
-*)
echo "$prog: invalid option -- $1"
echo "Try '$prog --help' for more information."
exit 1
;;
*)
arg=`echo "$1" | sed "s/'/'\"'\"'/g"`
SVNLOGCMD="$SVNLOGCMD '$arg'"
SVNINFOCMD="$SVNINFOCMD '$arg'"
shift
;;
esac
done
# find the directory that this script resides in
prog="$0"
while [ -h "$prog" ]
do
dir=`dirname "$prog"`
prog=`ls -ld "$prog" | sed "s/^.*-> \(.*\)/\1/;/^[^/]/s,^,$dir/,"`
done
dir=`dirname "$prog"`
dir=`cd "$dir" && pwd`
XSL="$dir/svn2${OUTSTYLE}.xsl"
# check if the authors file is formatted as a legacy
# colon separated file
if [ -n "$AUTHORSFILE" ] && \
egrep '^(#.*|[a-zA-Z0-9].*:)' "$AUTHORSFILE" > /dev/null 2>/dev/null
then
# create a temporary file
tmpfile=`mktemp -t svn2cl.XXXXXX 2> /dev/null || tempfile -s .svn2cl 2> /dev/null || echo "$AUTHORSFILE.$$.xml"`
arg=`echo "$tmpfile" | sed "s/'/'\"'\"'/g"`
TMPFILES="$TMPFILES '$arg'"
# generate an authors.xml file on the fly
echo '<authors>' > "$tmpfile"
sed -n 's/&/\&amp;/g;s/</\&lt;/g;s/>/\&gt;/g;s|^\([a-zA-Z0-9][^:]*\):\(.*\)$| <author uid="\1">\2</author>|p' \
< "$AUTHORSFILE" >> "$tmpfile"
echo '</authors>' >> "$tmpfile"
AUTHORSFILE="$tmpfile"
fi
# find the absolute path of the authors file
# (otherwise xsltproc will find the file relative to svn2cl.xsl)
pwd=`pwd`
AUTHORSFILE=`echo "$AUTHORSFILE" | sed "/^[^/]/s|^|$pwd/|"`
# if no filename was specified, make one up
if [ -z "$CHANGELOG" ]
then
CHANGELOG="ChangeLog"
if [ "$OUTSTYLE" != "cl" ]
then
CHANGELOG="$CHANGELOG.$OUTSTYLE"
fi
fi
# try to determin a prefix to strip from all paths
if [ "$STRIPPREFIX" = "AUTOMATICALLY-DETERMINED" ]
then
STRIPPREFIX=`LANG=C eval "$SVNINFOCMD" | $AWK '/^URL:/{url=$2} /^Repository Root:/{root=$3} END{if(root){print substr(url,length(root)+2)}else{n=split(url,u,"/");print u[n]}}'`
STRIPPREFIX=`echo "$STRIPPREFIX" | sed 's/%20/ /g'`
fi
# redirect stdout to the changelog file if needed
if [ "x$CHANGELOG" != "x-" ]
then
exec > "$CHANGELOG"
fi
# actually run the command we need
eval "$SVNLOGCMD" | \
xsltproc --stringparam strip-prefix "$STRIPPREFIX" \
--stringparam linelen "$LINELEN" \
--stringparam groupbyday "$GROUPBYDAY" \
--stringparam separate-daylogs "$SEPARATEDAYLOGS" \
--stringparam include-rev "$INCLUDEREV" \
--stringparam include-actions "$ACTIONS" \
--stringparam breakbeforemsg "$BREAKBEFOREMSG" \
--stringparam reparagraph "$REPARAGRAPH" \
--stringparam authorsfile "$AUTHORSFILE" \
--stringparam title "$TITLE" \
--stringparam revision-link "$REVISION_LINK" \
--stringparam ticket-link "$TICKET_LINK" \
--stringparam ignore-message-starting "$IGNORE_MESSAGE_STARTING" \
--nowrite \
--nomkdir \
--nonet \
"$XSL" -
# clean up temporary files
[ -n "$TMPFILES" ] && eval "rm -f $TMPFILES"
# we're done (the previous command could return false)
exit 0

457
tools/svn2cl/svn2cl.xsl Normal file
View File

@ -0,0 +1,457 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
svn2cl.xsl - xslt stylesheet for converting svn log to a normal
changelog
version 0.13
Usage (replace ++ with two minus signs which aren't allowed
inside xml comments):
svn ++verbose ++xml log | \
xsltproc ++stringparam strip-prefix `basename $(pwd)` \
++stringparam linelen 75 \
++stringparam groupbyday yes \
++stringparam separate-daylogs yes \
++stringparam include-rev yes \
++stringparam include-actions yes \
++stringparam breakbeforemsg yes/2 \
++stringparam reparagraph yes \
++stringparam authorsfile FILE \
++stringparam ignore-message-starting \
svn2cl.xsl - > ChangeLog
This file is based on several implementations of this conversion
that I was not completely happy with and some other common
xslt constructs found on the web.
Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Arthur de Jong.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
3. The name of the author may not be used to endorse or promote
products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
-->
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output
method="text"
encoding="utf-8"
media-type="text/plain"
omit-xml-declaration="yes"
standalone="yes"
indent="no" />
<xsl:strip-space elements="*" />
<!-- the prefix of pathnames to strip -->
<xsl:param name="strip-prefix" select="'/'" />
<!-- the length of a line to wrap messages at -->
<xsl:param name="linelen" select="75" />
<!-- whether entries should be grouped by day -->
<xsl:param name="groupbyday" select="'no'" />
<!-- whether to seperate log messages by empty lines -->
<xsl:param name="separate-daylogs" select="'no'" />
<!-- whether a revision number should be included -->
<xsl:param name="include-rev" select="'no'" />
<!-- whether aaction labels should be added to files -->
<xsl:param name="include-actions" select="'no'" />
<!-- whether the log message should start on a new line -->
<xsl:param name="breakbeforemsg" select="'no'" />
<!-- whether the message should be rewrapped within one paragraph -->
<xsl:param name="reparagraph" select="'no'" />
<!-- whether certain messages should be ignored -->
<xsl:param name="ignore-message-starting" select="''" />
<!-- location of authors file if any -->
<xsl:param name="authorsfile" select="''" />
<xsl:key name="author-lookup" match="author" use="@uid" />
<xsl:variable name="authors-top" select="document($authorsfile)/authors" />
<!-- determin the path part to strip -->
<xsl:variable name="strip-path">
<!-- if strip-prefix does not start with a slash, prepend it -->
<xsl:if test="not(starts-with($strip-prefix,'/'))">
<xsl:text>/</xsl:text>
</xsl:if>
<!-- the prefix itself -->
<xsl:value-of select="$strip-prefix" />
<!-- if strip-prefix does not start with a slash, append it -->
<xsl:if test="substring($strip-prefix,string-length($strip-prefix),1)!='/'">
<xsl:text>/</xsl:text>
</xsl:if>
</xsl:variable>
<!-- match the topmost log entry -->
<xsl:template match="log">
<xsl:choose>
<xsl:when test="$ignore-message-starting != ''">
<!-- only handle logentries with don't contain the string -->
<xsl:apply-templates select="logentry[not(starts-with(msg,$ignore-message-starting))]" />
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="logentry" />
</xsl:otherwise>
</xsl:choose>
<!-- add newlines at the end of the changelog -->
<xsl:text>&#10;</xsl:text>
</xsl:template>
<!-- format one entry from the log -->
<xsl:template match="logentry">
<xsl:choose>
<!-- if we're grouping we should omit some headers -->
<xsl:when test="$groupbyday='yes'">
<!-- fetch previous entry's date -->
<xsl:variable name="prevdate">
<xsl:apply-templates select="preceding-sibling::logentry[position()=1]/date" />
</xsl:variable>
<!-- fetch previous entry's author -->
<xsl:variable name="prevauthor">
<xsl:value-of select="normalize-space(preceding-sibling::logentry[position()=1]/author)" />
</xsl:variable>
<!-- fetch this entry's date -->
<xsl:variable name="date">
<xsl:apply-templates select="date" />
</xsl:variable>
<!-- fetch this entry's author -->
<xsl:variable name="author">
<xsl:value-of select="normalize-space(author)" />
</xsl:variable>
<!-- check if header is changed -->
<xsl:if test="($prevdate!=$date) or ($prevauthor!=$author)">
<!-- add newline -->
<xsl:if test="not(position()=1)">
<xsl:text>&#10;</xsl:text>
</xsl:if>
<!-- date -->
<xsl:value-of select="$date" />
<!-- two spaces -->
<xsl:text>&#32;&#32;</xsl:text>
<!-- author's name -->
<xsl:apply-templates select="author" />
<!-- two newlines -->
<xsl:text>&#10;</xsl:text>
<xsl:if test="$separate-daylogs!='yes'"><xsl:text>&#10;</xsl:text></xsl:if>
</xsl:if>
</xsl:when>
<!-- write the log header -->
<xsl:otherwise>
<!-- add newline -->
<xsl:if test="not(position()=1)">
<xsl:text>&#10;</xsl:text>
</xsl:if>
<!-- date -->
<xsl:apply-templates select="date" />
<!-- two spaces -->
<xsl:text>&#32;&#32;</xsl:text>
<!-- author's name -->
<xsl:apply-templates select="author" />
<!-- two newlines -->
<xsl:text>&#10;&#10;</xsl:text>
</xsl:otherwise>
</xsl:choose>
<!-- get paths string -->
<xsl:variable name="paths">
<xsl:apply-templates select="paths" />
</xsl:variable>
<!-- get revision number -->
<xsl:variable name="rev">
<xsl:if test="$include-rev='yes'">
<xsl:text>[r</xsl:text>
<xsl:value-of select="@revision" />
<xsl:text>]&#32;</xsl:text>
</xsl:if>
</xsl:variable>
<!-- trim trailing newlines -->
<xsl:variable name="msg">
<!-- add a line break before the log message -->
<xsl:choose>
<xsl:when test="$breakbeforemsg='yes'">
<xsl:text>&#10;</xsl:text>
</xsl:when>
<xsl:when test="number($breakbeforemsg)&gt;0">
<xsl:call-template name="newlines">
<xsl:with-param name="count" select="number($breakbeforemsg)" />
</xsl:call-template>
</xsl:when>
</xsl:choose>
<xsl:call-template name="trim-newln">
<xsl:with-param name="txt" select="msg" />
</xsl:call-template>
</xsl:variable>
<!-- add newline here if separate-daylogs is in effect -->
<xsl:if test="$groupbyday='yes' and $separate-daylogs='yes'"><xsl:text>&#10;</xsl:text></xsl:if>
<!-- first line is indented (other indents are done in wrap template) -->
<xsl:text>&#9;*&#32;</xsl:text>
<!-- set up the text to wrap -->
<xsl:variable name="txt">
<xsl:value-of select="$rev" />
<xsl:if test="$paths!=''">
<xsl:value-of select="concat($paths,':&#32;')" />
</xsl:if>
<xsl:value-of select="$msg" />
</xsl:variable>
<!-- print the paths and message nicely wrapped -->
<xsl:call-template name="wrap">
<xsl:with-param name="txt" select="$txt" />
</xsl:call-template>
</xsl:template>
<!-- format date -->
<xsl:template match="date">
<xsl:variable name="date" select="normalize-space(.)" />
<!-- output date part -->
<xsl:value-of select="substring($date,1,10)" />
<!-- output time part -->
<xsl:if test="$groupbyday!='yes'">
<xsl:text>&#32;</xsl:text>
<xsl:value-of select="substring($date,12,5)" />
</xsl:if>
</xsl:template>
<!-- format author -->
<xsl:template match="author">
<xsl:variable name="uid" select="normalize-space(.)" />
<!-- try to lookup author in authorsfile -->
<xsl:choose>
<xsl:when test="$authorsfile!=''">
<xsl:for-each select="$authors-top">
<xsl:variable name="author" select="key('author-lookup',$uid)" />
<!-- present result -->
<xsl:choose>
<xsl:when test="string($author/.)">
<xsl:apply-templates select="$author/node()" mode="copy" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$uid" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$uid" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- copy but normalize text -->
<xsl:template match="text()" mode="copy">
<xsl:value-of select="normalize-space(.)" />
</xsl:template>
<!-- simple copy template -->
<xsl:template match="@*|node()" mode="copy">
<xsl:copy>
<xsl:apply-templates select="@*|node()" mode="copy" />
</xsl:copy>
</xsl:template>
<!-- present a list of paths names -->
<xsl:template match="paths">
<xsl:choose>
<!-- only handle paths that begin with the path and strip the path -->
<xsl:when test="$strip-prefix != ''">
<!-- filter on all entries within directory -->
<xsl:for-each select="path[starts-with(concat(normalize-space(.),'/'),$strip-path)]">
<xsl:sort select="normalize-space(.)" data-type="text" />
<!-- unless we are the first entry, add a comma -->
<xsl:if test="not(position()=1)">
<xsl:text>,&#32;</xsl:text>
</xsl:if>
<!-- get path part -->
<xsl:variable name="path" select="substring(normalize-space(.),string-length($strip-path)+1)" />
<!-- translate empty string to dot and print result -->
<xsl:if test="$path = ''">
<xsl:text>.</xsl:text>
</xsl:if>
<xsl:value-of select="$path" />
<!-- add the action flag -->
<xsl:if test="$include-actions='yes'">
<xsl:apply-templates select="." mode="action"/>
</xsl:if>
</xsl:for-each>
</xsl:when>
<!-- print a simple list of all paths -->
<xsl:otherwise>
<xsl:for-each select="path">
<xsl:sort select="normalize-space(.)" data-type="text" />
<!-- unless we are the first entry, add a comma -->
<xsl:if test="not(position()=1)">
<xsl:text>,&#32;</xsl:text>
</xsl:if>
<!-- print the path name -->
<xsl:value-of select="normalize-space(.)" />
<!-- add the action flag -->
<xsl:if test="$include-actions='yes'">
<xsl:apply-templates select="." mode="action"/>
</xsl:if>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="path" mode="action">
<xsl:choose>
<xsl:when test="@action='D'">
<xsl:text>[DEL]</xsl:text>
</xsl:when>
<xsl:when test="@copyfrom-path">
<xsl:text>[CPY]</xsl:text>
</xsl:when>
<xsl:when test="@action='A'">
<xsl:text>[ADD]</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:template>
<!-- string-wrapping template -->
<xsl:template name="wrap">
<xsl:param name="txt" />
<xsl:variable name="normtxt" select="normalize-space($txt)" />
<xsl:choose>
<xsl:when test="contains($txt,'&#10;')">
<!-- text contains newlines, do the first line -->
<xsl:call-template name="wrap">
<xsl:with-param name="txt" select="substring-before($txt,'&#10;')" />
</xsl:call-template>
<!-- print tab -->
<xsl:text>&#9;&#32;&#32;</xsl:text>
<!-- wrap the rest of the text -->
<xsl:call-template name="wrap">
<xsl:with-param name="txt" select="substring-after($txt,'&#10;')" />
</xsl:call-template>
</xsl:when>
<xsl:when test="(string-length($normtxt) &lt; (($linelen)-9)) or not(contains($normtxt,' '))">
<!-- this is easy, nothing to do -->
<xsl:value-of select="$normtxt" />
<!-- add newline -->
<xsl:text>&#10;</xsl:text>
</xsl:when>
<xsl:otherwise>
<!-- find the first line -->
<xsl:variable name="tmp" select="substring($normtxt,1,(($linelen)-9))" />
<xsl:variable name="line">
<xsl:choose>
<!-- if our attempt contains spaces wrap on that -->
<xsl:when test="contains($tmp,' ')">
<xsl:call-template name="find-line">
<xsl:with-param name="txt" select="$tmp" />
</xsl:call-template>
</xsl:when>
<!-- otherwise use the first non-space characters from the text -->
<xsl:otherwise>
<xsl:value-of select="substring-before($normtxt,' ')" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- print line -->
<xsl:value-of select="$line" />
<!-- print newline and tab -->
<xsl:text>&#10;&#9;&#32;&#32;</xsl:text>
<!-- wrap the rest of the text -->
<xsl:call-template name="wrap">
<xsl:with-param name="txt" select="normalize-space(substring($normtxt,string-length($line)+1))" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- template to trim line to contain space as last char -->
<xsl:template name="find-line">
<xsl:param name="txt" />
<xsl:choose>
<xsl:when test="substring($txt,string-length($txt),1)=' '">
<xsl:value-of select="substring($txt,1,string-length($txt)-1)" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="find-line">
<xsl:with-param name="txt" select="substring($txt,1,string-length($txt)-1)" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- template to trim trailing and starting newlines -->
<xsl:template name="trim-newln">
<xsl:param name="txt" />
<xsl:choose>
<!-- find starting newlines -->
<xsl:when test="substring($txt,1,1) = '&#10;'">
<xsl:call-template name="trim-newln">
<xsl:with-param name="txt" select="substring($txt,2)" />
</xsl:call-template>
</xsl:when>
<!-- find trailing newlines -->
<xsl:when test="substring($txt,string-length($txt),1) = '&#10;'">
<xsl:call-template name="trim-newln">
<xsl:with-param name="txt" select="substring($txt,1,string-length($txt)-1)" />
</xsl:call-template>
</xsl:when>
<!-- if the message has paragraphs, find the first one -->
<xsl:when test="$reparagraph='yes' and contains($txt,'&#10;&#10;')">
<!-- remove newlines from first paragraph -->
<xsl:value-of select="normalize-space(substring-before($txt,'&#10;&#10;'))" />
<!-- paragraph separator -->
<xsl:text>&#10;&#10;</xsl:text>
<!-- do the rest of the text -->
<xsl:call-template name="trim-newln">
<xsl:with-param name="txt" select="substring-after($txt,'&#10;&#10;')" />
</xsl:call-template>
</xsl:when>
<!-- remove more single newlines -->
<xsl:when test="$reparagraph='yes'">
<xsl:value-of select="normalize-space($txt)" />
</xsl:when>
<!-- no newlines found, we're done -->
<xsl:otherwise>
<xsl:value-of select="$txt" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- insert a number of newlines -->
<xsl:template name="newlines">
<xsl:param name="count" />
<xsl:text>&#10;</xsl:text>
<xsl:if test="$count&gt;1">
<xsl:call-template name="newlines">
<xsl:with-param name="count" select="($count)-1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>