blob: a400253b5fa76b9a2460d7f7bdbdf1be7912e9de [file] [log] [blame]
/*
* xml_output.c: output a Subversion "tree-delta" XML stream
*
* ====================================================================
* Copyright (c) 2000-2002 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "svn_types.h"
#include "svn_string.h"
#include "svn_path.h"
#include "svn_error.h"
#include "svn_delta.h"
#include "svn_io.h"
#include "svn_xml.h"
#include "svn_base64.h"
#include "svn_quoprint.h"
#include "svn_pools.h"
#include "delta.h"
/* TODO:
- Produce real vcdiff data when we have Branko's text delta ->
vcdiff routines.
- Do consistency checking on the order of calls, maybe.
(Right now we'll just spit out invalid output if the calls
come in an incorrect order.)
- Indentation? Not really a priority.
*/
/* The types of some of the elements we output. The actual range of
valid values is always narrower than the full set, but they
overlap, so it doesn't quite make sense to have a separate
enueration for each use. */
enum elemtype {
elem_delta_pkg,
elem_add,
elem_open,
elem_dir,
elem_dir_prop_delta,
elem_tree_delta,
elem_file,
elem_file_prop_delta
};
struct edit_baton
{
svn_stream_t *output;
enum elemtype elem; /* Current element we are inside at the end of
a call. One of elem_dir_prop_delta,
elem_tree_delta, elem_file, elem_dir, or
elem_file_prop_delta. */
struct file_baton *curfile;
svn_revnum_t target_revision;
apr_pool_t *pool;
int txdelta_id_counter;
};
struct dir_baton
{
struct edit_baton *edit_baton;
enum elemtype addopen; /* elem_add or elem_open, or
elem_delta_pkg for the root
directory. */
apr_pool_t *pool;
};
struct file_baton
{
struct edit_baton *edit_baton;
enum elemtype addopen;
int txdelta_id; /* ID of deferred text delta;
0 means we're still working on the file,
-1 means we already saw a text delta. */
int closed; /* 1 if we closed the element already. */
apr_pool_t *pool;
};
/* Convenience macro. */
#define STR_BUF_LU(p,lu) svn_stringbuf_createf (p, "%lu", (unsigned long) lu)
static struct dir_baton *
make_dir_baton (struct edit_baton *eb,
enum elemtype addopen,
apr_pool_t *pool)
{
struct dir_baton *db = apr_palloc (pool, sizeof (*db));
db->edit_baton = eb;
db->addopen = addopen;
db->pool = pool;
return db;
}
static struct file_baton *
make_file_baton (struct edit_baton *eb,
enum elemtype addopen,
apr_pool_t *pool)
{
struct file_baton *fb = apr_palloc (pool, sizeof (*fb));
fb->edit_baton = eb;
fb->addopen = addopen;
fb->txdelta_id = 0;
fb->closed = 0;
fb->pool = pool;
return fb;
}
/* The meshing between the editor interface and the XML delta format
is such that we can't usually output the end of an element until we
go on to the next thing, and for a given call we may or may not
have already output the beginning of the element we're working on.
This function takes care of "unwinding" and "winding" from the
current element to the kind of element we need to work on next. We
never have to unwind past a dir element, so the unwinding steps are
bounded in number and easy to visualize. The nesting of the
elements we care about looks like:
dir -> prop_delta
-> tree_delta -> add/open -> file -> prop_delta
We cannot be in an add/open element at the end of a call, so
add/open and file are treated as a unit by this function. Note
that although there is no open or dir element corresponding to
the root directory (the root directory's tree-delta and/or
prop-delta elements live directly inside the delta-pkg element), we
pretend that there is for the sake of regularity.
This function will "unwind" arbitrarily within that little tree,
but will only "wind" from dir to tree_delta or prop_delta or from
file to prop_delta. Winding through add/open/file would require
extra information.
ELEM specifies the element type we want to get to, with prop_delta
split out into elem_dir_prop_delta and elem_file_prop_delta
depending on where the prop_delta is in the little tree. The
element type we are currently in is recorded inside EB. */
static svn_stringbuf_t *
get_to_elem (struct edit_baton *eb, enum elemtype elem, apr_pool_t *pool)
{
svn_stringbuf_t *str = svn_stringbuf_create ("", pool);
struct file_baton *fb;
/*** Unwind. Start from the leaves and go back as far as necessary. */
if ((eb->elem == elem_file_prop_delta) && (elem != elem_file_prop_delta))
{
svn_xml_make_close_tag (&str, pool, SVN_DELTA__XML_TAG_PROP_DELTA);
eb->elem = elem_file;
}
if ((eb->elem == elem_file)
&& (elem != elem_file) && (elem != elem_file_prop_delta))
{
const char *outertag = ((eb->curfile->addopen == elem_add)
? SVN_DELTA__XML_TAG_ADD
: SVN_DELTA__XML_TAG_OPEN);
fb = eb->curfile;
if (fb->txdelta_id == 0)
{
svn_stringbuf_t *idstr;
fb->txdelta_id = eb->txdelta_id_counter++;
idstr = svn_stringbuf_createf (pool, "%d", fb->txdelta_id);
svn_xml_make_open_tag (&str, pool, svn_xml_self_closing,
SVN_DELTA__XML_TAG_TEXT_DELTA_REF,
SVN_DELTA__XML_ATTR_ID, idstr, NULL);
}
svn_xml_make_close_tag (&str, pool, SVN_DELTA__XML_TAG_FILE);
svn_xml_make_close_tag (&str, pool, outertag);
fb->closed = 1;
eb->curfile = NULL;
eb->elem = elem_tree_delta;
}
if ((eb->elem == elem_tree_delta)
&& ((elem == elem_dir) || (elem == elem_dir_prop_delta)))
{
svn_xml_make_close_tag (&str, pool, SVN_DELTA__XML_TAG_TREE_DELTA);
eb->elem = elem_dir;
}
if ((eb->elem == elem_dir_prop_delta) && (elem != elem_dir_prop_delta))
{
svn_xml_make_close_tag (&str, pool, SVN_DELTA__XML_TAG_PROP_DELTA);
eb->elem = elem_dir;
}
/*** Now wind. */
if ((eb->elem == elem_dir) && (elem == elem_tree_delta))
{
svn_xml_make_open_tag (&str, pool, svn_xml_normal,
SVN_DELTA__XML_TAG_TREE_DELTA, NULL);
eb->elem = elem_tree_delta;
}
if (((eb->elem == elem_dir) && (elem == elem_dir_prop_delta))
|| ((eb->elem == elem_file) && (elem == elem_file_prop_delta)))
{
svn_xml_make_open_tag (&str, pool, svn_xml_normal,
SVN_DELTA__XML_TAG_PROP_DELTA, NULL);
eb->elem = elem;
}
/* If we didn't make it to the type of element the caller asked for,
either the caller wants us to do something we don't do or we have
a bug. */
assert (eb->elem == elem);
return str;
}
/* Output XML for adding or replacing a file or directory. Also set
EB->elem to the value of DIRFILE for consistency. */
static svn_error_t *
output_addopen (struct edit_baton *eb,
enum elemtype addopen,
enum elemtype dirfile,
const char *path,
const char *base_path,
svn_revnum_t base_revision,
apr_pool_t *pool)
{
svn_stringbuf_t *str;
apr_size_t len;
apr_hash_t *att;
const char *outertag = ((addopen == elem_add)
? SVN_DELTA__XML_TAG_ADD
: SVN_DELTA__XML_TAG_OPEN);
const char *innertag = ((dirfile == elem_dir)
? SVN_DELTA__XML_TAG_DIR
: SVN_DELTA__XML_TAG_FILE);
svn_stringbuf_t *name =
svn_stringbuf_create (svn_path_basename (path, pool), pool);
str = get_to_elem (eb, elem_tree_delta, pool);
svn_xml_make_open_tag (&str, pool, svn_xml_normal, outertag,
SVN_DELTA__XML_ATTR_NAME, name, NULL);
att = apr_hash_make (pool);
if ((addopen == elem_add) && (base_path != NULL))
apr_hash_set (att, SVN_DELTA__XML_ATTR_COPYFROM_PATH,
strlen (SVN_DELTA__XML_ATTR_COPYFROM_PATH), base_path);
if (SVN_IS_VALID_REVNUM (base_revision))
{
svn_stringbuf_t *buf = STR_BUF_LU (pool, base_revision);
if (addopen == elem_add)
apr_hash_set (att, SVN_DELTA__XML_ATTR_COPYFROM_REV,
strlen (SVN_DELTA__XML_ATTR_COPYFROM_REV), buf);
else
apr_hash_set (att, SVN_DELTA__XML_ATTR_BASE_REV,
strlen (SVN_DELTA__XML_ATTR_BASE_REV), buf);
}
svn_xml_make_open_tag_hash (&str, pool, svn_xml_normal, innertag, att);
eb->elem = dirfile;
len = str->len;
return svn_stream_write (eb->output, str->data, &len);
}
/* Output a set or delete element. ELEM is the type of prop-delta
(elem_dir_prop_delta or elem_file_prop_delta) the element lives
in. This function sets EB->elem to ELEM for consistency. */
static svn_error_t *
output_propset (struct edit_baton *eb,
enum elemtype elem,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
svn_stringbuf_t *str;
apr_size_t len;
svn_stringbuf_t *name_str = svn_stringbuf_create (name, pool);
svn_stringbuf_t *val_str = ((value == NULL)
? NULL
: svn_stringbuf_create_from_string (value, pool));
str = get_to_elem (eb, elem, pool);
if (val_str)
{
svn_xml_make_open_tag (&str, pool, svn_xml_protect_pcdata,
SVN_DELTA__XML_TAG_SET,
SVN_DELTA__XML_ATTR_NAME,
name_str, NULL);
svn_xml_escape_stringbuf (&str, val_str, pool);
svn_xml_make_close_tag (&str, pool, SVN_DELTA__XML_TAG_SET);
}
else
svn_xml_make_open_tag (&str, pool, svn_xml_self_closing,
SVN_DELTA__XML_TAG_DELETE,
SVN_DELTA__XML_ATTR_NAME, name_str, NULL);
len = str->len;
return svn_stream_write (eb->output, str->data, &len);
}
static svn_error_t *
set_target_revision (void *edit_baton,
svn_revnum_t target_revision)
{
struct edit_baton *eb = edit_baton;
/* Stick that target revision in the edit baton to be used when
we call open_root() */
eb->target_revision = target_revision;
return SVN_NO_ERROR;
}
static svn_error_t *
open_root (void *edit_baton,
svn_revnum_t base_revision,
apr_pool_t *pool,
void **dir_baton)
{
struct edit_baton *eb = edit_baton;
svn_stringbuf_t *str = NULL;
apr_size_t len;
apr_hash_t *att;
svn_xml_make_header (&str, pool);
att = apr_hash_make (pool);
if (SVN_IS_VALID_REVNUM (base_revision))
apr_hash_set (att, SVN_DELTA__XML_ATTR_BASE_REV,
strlen (SVN_DELTA__XML_ATTR_BASE_REV),
STR_BUF_LU (pool, base_revision));
if (SVN_IS_VALID_REVNUM (eb->target_revision))
apr_hash_set (att, SVN_DELTA__XML_ATTR_TARGET_REV,
strlen (SVN_DELTA__XML_ATTR_TARGET_REV),
STR_BUF_LU (pool, eb->target_revision));
svn_xml_make_open_tag_hash (&str, pool, svn_xml_normal,
SVN_DELTA__XML_TAG_DELTA_PKG, att);
*dir_baton = make_dir_baton (eb, elem_delta_pkg, pool);
eb->elem = elem_dir;
len = str->len;
return svn_stream_write (eb->output, str->data, &len);
}
static svn_error_t *
delete_entry (const char *path,
svn_revnum_t revision,
void *parent_baton,
apr_pool_t *pool)
{
struct dir_baton *db = parent_baton;
struct edit_baton *eb = db->edit_baton;
svn_stringbuf_t *str;
apr_hash_t *att;
apr_size_t len;
svn_stringbuf_t *name =
svn_stringbuf_create (svn_path_basename (path, pool), pool);
str = get_to_elem (eb, elem_tree_delta, pool);
att = apr_hash_make (pool);
apr_hash_set (att, SVN_DELTA__XML_ATTR_NAME,
strlen (SVN_DELTA__XML_ATTR_NAME), name);
if (SVN_IS_VALID_REVNUM (revision))
apr_hash_set (att, SVN_DELTA__XML_ATTR_BASE_REV,
strlen (SVN_DELTA__XML_ATTR_BASE_REV),
STR_BUF_LU (pool, revision));
svn_xml_make_open_tag_hash (&str, pool, svn_xml_self_closing,
SVN_DELTA__XML_TAG_DELETE, att);
len = str->len;
return svn_stream_write (eb->output, str->data, &len);
}
static svn_error_t *
add_directory (const char *path,
void *parent_baton,
const char *copyfrom_path,
svn_revnum_t copyfrom_revision,
apr_pool_t *pool,
void **child_baton)
{
struct dir_baton *db = parent_baton;
struct edit_baton *eb = db->edit_baton;
*child_baton = make_dir_baton (eb, elem_add, pool);
return output_addopen (eb, elem_add, elem_dir, path,
copyfrom_path, copyfrom_revision, pool);
}
static svn_error_t *
open_directory (const char *path,
void *parent_baton,
svn_revnum_t base_revision,
apr_pool_t *pool,
void **child_baton)
{
struct dir_baton *db = parent_baton;
struct edit_baton *eb = db->edit_baton;
*child_baton = make_dir_baton (eb, elem_open, pool);
return output_addopen (eb, elem_open, elem_dir, path,
NULL, base_revision, pool);
}
static svn_error_t *
change_dir_prop (void *dir_baton,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
struct dir_baton *db = dir_baton;
struct edit_baton *eb = db->edit_baton;
return output_propset (eb, elem_dir_prop_delta, name, value, pool);
}
static svn_error_t *
close_directory (void *dir_baton)
{
struct dir_baton *db = dir_baton;
struct edit_baton *eb = db->edit_baton;
svn_stringbuf_t *str;
apr_size_t len;
str = get_to_elem (eb, elem_dir, db->pool);
if (db->addopen != elem_delta_pkg)
{
/* Not the root directory. */
const char *outertag = ((db->addopen == elem_add)
? SVN_DELTA__XML_TAG_ADD
: SVN_DELTA__XML_TAG_OPEN);
svn_xml_make_close_tag (&str, db->pool, SVN_DELTA__XML_TAG_DIR);
svn_xml_make_close_tag (&str, db->pool, outertag);
eb->elem = elem_tree_delta;
}
else
eb->elem = elem_delta_pkg;
len = str->len;
return svn_stream_write (eb->output, str->data, &len);
}
static svn_error_t *
add_file (const char *path,
void *parent_baton,
const char *copyfrom_path,
svn_revnum_t copyfrom_revision,
apr_pool_t *pool,
void **file_baton)
{
struct dir_baton *db = parent_baton;
struct edit_baton *eb = db->edit_baton;
SVN_ERR (output_addopen (eb, elem_add, elem_file, path,
copyfrom_path, copyfrom_revision, pool));
*file_baton = make_file_baton (eb, elem_add, pool);
eb->curfile = *file_baton;
return SVN_NO_ERROR;
}
static svn_error_t *
open_file (const char *name,
void *parent_baton,
svn_revnum_t base_revision,
apr_pool_t *pool,
void **file_baton)
{
struct dir_baton *db = parent_baton;
struct edit_baton *eb = db->edit_baton;
SVN_ERR (output_addopen (eb, elem_open, elem_file, name,
NULL, base_revision, pool));
*file_baton = make_file_baton (eb, elem_open, pool);
eb->curfile = *file_baton;
return SVN_NO_ERROR;
}
static svn_error_t *
output_svndiff_data (void *baton,
const char *data,
apr_size_t *len)
{
struct file_baton *fb = baton;
struct edit_baton *eb = fb->edit_baton;
/* Just pass through the write request to the editor's output stream. */
return svn_stream_write (eb->output, data, len);
}
static svn_error_t *
finish_svndiff_data (void *baton)
{
struct file_baton *fb = baton;
struct edit_baton *eb = fb->edit_baton;
apr_pool_t *subpool = svn_pool_create (eb->pool);
svn_stringbuf_t *str = NULL;
svn_error_t *err;
apr_size_t slen;
svn_xml_make_close_tag (&str, subpool, SVN_DELTA__XML_TAG_TEXT_DELTA);
slen = str->len;
err = svn_stream_write (eb->output, str->data, &slen);
svn_pool_destroy (subpool);
return err;
}
static svn_error_t *
apply_textdelta (void *file_baton,
svn_txdelta_window_handler_t *handler,
void **handler_baton)
{
struct file_baton *fb = file_baton;
struct edit_baton *eb = fb->edit_baton;
svn_stringbuf_t *str = NULL;
apr_pool_t *pool = svn_pool_create (eb->pool);
svn_error_t *err;
apr_size_t len;
svn_stream_t *output, *encoder;
apr_hash_t *att;
att = apr_hash_make (pool);
if (fb->txdelta_id == 0)
{
/* We are inside a file element (possibly in a prop-delta) and
are outputting a text-delta inline. */
str = get_to_elem (eb, elem_file, pool);
}
else
{
/* We should be at the end of the delta (after the root
directory has been closed) and are outputting a deferred
text-delta. */
char buf[128];
sprintf(buf, "%d", fb->txdelta_id);
apr_hash_set (att, SVN_DELTA__XML_ATTR_ID,
strlen(SVN_DELTA__XML_ATTR_ID),
svn_stringbuf_create (buf, pool));
}
#ifdef QUOPRINT_SVNDIFFS
apr_hash_set (att, SVN_DELTA__XML_ATTR_ENCODING,
strlen(SVN_DELTA__XML_ATTR_ENCODING),
svn_stringbuf_create ("quoted-printable", pool));
#endif
svn_xml_make_open_tag_hash (&str, pool, svn_xml_protect_pcdata,
SVN_DELTA__XML_TAG_TEXT_DELTA, att);
fb->txdelta_id = -1;
len = str->len;
err = svn_stream_write (eb->output, str->data, &len);
svn_pool_destroy (pool);
/* Set up a handler which will write base64-encoded svndiff data to
the editor's output stream. */
output = svn_stream_create (fb, fb->pool);
svn_stream_set_write (output, output_svndiff_data);
svn_stream_set_close (output, finish_svndiff_data);
#ifdef QUOPRINT_SVNDIFFS
encoder = svn_quoprint_encode (output, eb->pool);
#else
encoder = svn_base64_encode (output, eb->pool);
#endif
svn_txdelta_to_svndiff (encoder, eb->pool, handler, handler_baton);
return err;
}
static svn_error_t *
change_file_prop (void *file_baton,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
struct file_baton *fb = file_baton;
struct edit_baton *eb = fb->edit_baton;
return output_propset (eb, elem_file_prop_delta, name, value, pool);
}
static svn_error_t *
close_file (void *file_baton)
{
struct file_baton *fb = file_baton;
struct edit_baton *eb = fb->edit_baton;
svn_stringbuf_t *str;
svn_error_t *err = SVN_NO_ERROR;
apr_size_t len;
/* Close the file element if we are still working on it. */
if (! fb->closed)
{
const char *outertag = ((fb->addopen == elem_add)
? SVN_DELTA__XML_TAG_ADD
: SVN_DELTA__XML_TAG_OPEN);
str = get_to_elem (eb, elem_file, fb->pool);
svn_xml_make_close_tag (&str, fb->pool, SVN_DELTA__XML_TAG_FILE);
svn_xml_make_close_tag (&str, fb->pool, outertag);
len = str->len;
err = svn_stream_write (eb->output, str->data, &len);
eb->curfile = NULL;
eb->elem = elem_tree_delta;
}
return err;
}
static svn_error_t *
close_edit (void *edit_baton)
{
struct edit_baton *eb = edit_baton;
svn_error_t *err;
svn_stringbuf_t *str = NULL;
apr_size_t len;
svn_xml_make_close_tag (&str, eb->pool, SVN_DELTA__XML_TAG_DELTA_PKG);
len = str->len;
err = svn_stream_write (eb->output, str->data, &len);
if (err == SVN_NO_ERROR)
err = svn_stream_close (eb->output);
svn_pool_destroy (eb->pool);
return err;
}
svn_error_t *
svn_delta_get_xml_editor (svn_stream_t *output,
const svn_delta_editor_t **editor,
void **edit_baton,
apr_pool_t *pool)
{
struct edit_baton *eb;
apr_pool_t *subpool = svn_pool_create (pool);
svn_delta_editor_t *tree_editor = svn_delta_default_editor (pool);
/* Construct an edit baton. */
eb = apr_palloc (subpool, sizeof (*eb));
eb->pool = subpool;
eb->output = output;
eb->curfile = NULL;
eb->txdelta_id_counter = 1;
eb->target_revision = SVN_INVALID_REVNUM;
/* Construct an editor. */
tree_editor->set_target_revision = set_target_revision;
tree_editor->open_root = open_root;
tree_editor->delete_entry = delete_entry;
tree_editor->add_directory = add_directory;
tree_editor->open_directory = open_directory;
tree_editor->change_dir_prop = change_dir_prop;
tree_editor->close_directory = close_directory;
tree_editor->add_file = add_file;
tree_editor->open_file = open_file;
tree_editor->apply_textdelta = apply_textdelta;
tree_editor->change_file_prop = change_file_prop;
tree_editor->close_file = close_file;
tree_editor->close_edit = close_edit;
*edit_baton = eb;
*editor = tree_editor;
return SVN_NO_ERROR;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end:
*/