An XML builder.
Creates a new Builder that will write to a file.
filename (String) filename to write to
options - (Hash) formating options
:indent (Fixnum) indentaion level, negative values excludes
terminating newline
:size (Fixnum) the initial size of the string buffer
static VALUE
builder_file(int argc, VALUE *argv, VALUE self) {
Builder b = ALLOC(struct _Builder);
int indent = ox_default_options.indent;
long buf_size = 0;
FILE *f;
if (1 > argc) {
rb_raise(ox_arg_error_class, "missing filename");
}
Check_Type(*argv, T_STRING);
if (NULL == (f = fopen(StringValuePtr(*argv), "w"))) {
xfree(b);
rb_raise(rb_eIOError, "%s\n", strerror(errno));
}
if (2 == argc) {
volatile VALUE v;
rb_check_type(argv[1], T_HASH);
if (Qnil != (v = rb_hash_lookup(argv[1], ox_indent_sym))) {
#ifdef RUBY_INTEGER_UNIFICATION
if (rb_cInteger != rb_obj_class(v)) {
#else
if (rb_cFixnum != rb_obj_class(v)) {
#endif
rb_raise(ox_parse_error_class, ":indent must be a fixnum.\n");
}
indent = NUM2INT(v);
}
if (Qnil != (v = rb_hash_lookup(argv[1], ox_size_sym))) {
#ifdef RUBY_INTEGER_UNIFICATION
if (rb_cInteger != rb_obj_class(v)) {
#else
if (rb_cFixnum != rb_obj_class(v)) {
#endif
rb_raise(ox_parse_error_class, ":size must be a fixnum.\n");
}
buf_size = NUM2LONG(v);
}
}
b->file = f;
init(b, fileno(f), indent, buf_size);
if (rb_block_given_p()) {
volatile VALUE rb = Data_Wrap_Struct(builder_class, NULL, builder_free, b);
rb_yield(rb);
bclose(b);
return Qnil;
} else {
return Data_Wrap_Struct(builder_class, NULL, builder_free, b);
}
}
Creates a new Builder that will write to an IO instance.
io (String) IO to write to
options - (Hash) formating options
:indent (Fixnum) indentaion level, negative values excludes
terminating newline
:size (Fixnum) the initial size of the string buffer
static VALUE
builder_io(int argc, VALUE *argv, VALUE self) {
Builder b = ALLOC(struct _Builder);
int indent = ox_default_options.indent;
long buf_size = 0;
int fd;
volatile VALUE v;
if (1 > argc) {
rb_raise(ox_arg_error_class, "missing IO object");
}
if (!rb_respond_to(*argv, ox_fileno_id) ||
Qnil == (v = rb_funcall(*argv, ox_fileno_id, 0)) ||
0 == (fd = FIX2INT(v))) {
rb_raise(rb_eIOError, "expected an IO that has a fileno.");
}
if (2 == argc) {
volatile VALUE v;
rb_check_type(argv[1], T_HASH);
if (Qnil != (v = rb_hash_lookup(argv[1], ox_indent_sym))) {
#ifdef RUBY_INTEGER_UNIFICATION
if (rb_cInteger != rb_obj_class(v)) {
#else
if (rb_cFixnum != rb_obj_class(v)) {
#endif
rb_raise(ox_parse_error_class, ":indent must be a fixnum.\n");
}
indent = NUM2INT(v);
}
if (Qnil != (v = rb_hash_lookup(argv[1], ox_size_sym))) {
#ifdef RUBY_INTEGER_UNIFICATION
if (rb_cInteger != rb_obj_class(v)) {
#else
if (rb_cFixnum != rb_obj_class(v)) {
#endif
rb_raise(ox_parse_error_class, ":size must be a fixnum.\n");
}
buf_size = NUM2LONG(v);
}
}
b->file = NULL;
init(b, fd, indent, buf_size);
if (rb_block_given_p()) {
volatile VALUE rb = Data_Wrap_Struct(builder_class, NULL, builder_free, b);
rb_yield(rb);
bclose(b);
return Qnil;
} else {
return Data_Wrap_Struct(builder_class, NULL, builder_free, b);
}
}
Creates a new Builder that will write to a string that can be retrieved with the #to_s() method. If a block is given it is executed with a single parameter which is the builder instance. The return value is then the generated string.
options - (Hash) formating options
:indent (Fixnum) indentaion level, negative values excludes
terminating newline
:size (Fixnum) the initial size of the string buffer
static VALUE
builder_new(int argc, VALUE *argv, VALUE self) {
Builder b = ALLOC(struct _Builder);
int indent = ox_default_options.indent;
long buf_size = 0;
if (1 == argc) {
volatile VALUE v;
rb_check_type(*argv, T_HASH);
if (Qnil != (v = rb_hash_lookup(*argv, ox_indent_sym))) {
#ifdef RUBY_INTEGER_UNIFICATION
if (rb_cInteger != rb_obj_class(v)) {
#else
if (rb_cFixnum != rb_obj_class(v)) {
#endif
rb_raise(ox_parse_error_class, ":indent must be a fixnum.\n");
}
indent = NUM2INT(v);
}
if (Qnil != (v = rb_hash_lookup(*argv, ox_size_sym))) {
#ifdef RUBY_INTEGER_UNIFICATION
if (rb_cInteger != rb_obj_class(v)) {
#else
if (rb_cFixnum != rb_obj_class(v)) {
#endif
rb_raise(ox_parse_error_class, ":size must be a fixnum.\n");
}
buf_size = NUM2LONG(v);
}
}
b->file = NULL;
init(b, 0, indent, buf_size);
if (rb_block_given_p()) {
volatile VALUE rb = Data_Wrap_Struct(builder_class, NULL, builder_free, b);
rb_yield(rb);
bclose(b);
return to_s(b);
} else {
return Data_Wrap_Struct(builder_class, NULL, builder_free, b);
}
}
Adds a CDATA element to the XML string being formed.
data - (String) contents of the CDATA element
static VALUE
builder_cdata(VALUE self, VALUE data) {
Builder b = (Builder)DATA_PTR(self);
volatile VALUE v = data;
const char *str;
const char *s;
const char *end;
int len;
if (T_STRING != rb_type(v)) {
v = rb_funcall(v, ox_to_s_id, 0);
}
str = StringValuePtr(v);
len = (int)RSTRING_LEN(v);
s = str;
end = str + len;
i_am_a_child(b, false);
append_indent(b);
buf_append_string(&b->buf, "<![CDATA[", 9);
b->col += 9;
b->pos += 9;
buf_append_string(&b->buf, str, len);
b->col += len;
s = strchr(s, '\n');
while (NULL != s) {
b->line++;
b->col = end - s;
s = strchr(s + 1, '\n');
}
b->pos += len;
buf_append_string(&b->buf, "]]>", 3);
b->col += 3;
b->pos += 3;
return Qnil;
}
Closes the all elements and the document.
static VALUE
builder_close(VALUE self) {
bclose((Builder)DATA_PTR(self));
return Qnil;
}
Returns the current column in the output. The first character in a line is at column 1.
static VALUE
builder_column(VALUE self) {
return LONG2NUM(((Builder)DATA_PTR(self))->col);
}
Adds a comment element to the XML string being formed.
text - (String) contents of the comment
static VALUE
builder_comment(VALUE self, VALUE text) {
Builder b = (Builder)DATA_PTR(self);
rb_check_type(text, T_STRING);
i_am_a_child(b, false);
append_indent(b);
buf_append_string(&b->buf, "<!-- ", 5);
b->col += 5;
b->pos += 5;
append_string(b, StringValuePtr(text), RSTRING_LEN(text), xml_element_chars);
buf_append_string(&b->buf, " --/> ", 5);
b->col += 5;
b->pos += 5;
return Qnil;
}
Adds a DOCTYPE element to the XML string being formed.
text - (String) contents of the doctype
static VALUE
builder_doctype(VALUE self, VALUE text) {
Builder b = (Builder)DATA_PTR(self);
rb_check_type(text, T_STRING);
i_am_a_child(b, false);
append_indent(b);
buf_append_string(&b->buf, "<!DOCTYPE ", 10);
b->col += 10;
b->pos += 10;
append_string(b, StringValuePtr(text), RSTRING_LEN(text), xml_element_chars);
buf_append(&b->buf, '>');
b->col++;
b->pos++;
return Qnil;
}
Adds an element with the name and attributes provided. If a block is given then on closing of the block a pop() done at the close of the block.
name - (String) name of the element
attributes - (Hash) of the element
static VALUE
builder_element(int argc, VALUE *argv, VALUE self) {
Builder b = (Builder)DATA_PTR(self);
Element e;
const char *name;
int len;
if (1 > argc) {
rb_raise(ox_arg_error_class, "missing element name");
}
i_am_a_child(b, false);
append_indent(b);
b->depth++;
if (MAX_DEPTH <= b->depth) {
rb_raise(ox_arg_error_class, "XML too deeply nested");
}
switch (rb_type(*argv)) {
case T_STRING:
name = StringValuePtr(*argv);
len = RSTRING_LEN(*argv);
break;
case T_SYMBOL:
name = rb_id2name(SYM2ID(*argv));
len = strlen(name);
break;
default:
rb_raise(ox_arg_error_class, "expected a Symbol or String for an element name");
break;
}
e = &b->stack[b->depth];
if (sizeof(e->buf) <= (size_t)len) {
e->name = strdup(name);
*e->buf = '\0';
} else {
strcpy(e->buf, name);
e->name = e->buf;
}
e->len = len;
e->has_child = false;
e->non_text_child = false;
buf_append(&b->buf, '<');
b->col++;
b->pos++;
append_string(b, e->name, len, xml_element_chars);
if (1 < argc) {
rb_hash_foreach(argv[1], append_attr, (VALUE)b);
}
// Do not close with > or /> yet. That is done with i_am_a_child() or pop().
if (rb_block_given_p()) {
rb_yield(self);
pop(b);
}
return Qnil;
}
Adds the top level <?xml?> element.
decl - (String) 'xml' expected
options - (Hash) version or encoding
static VALUE
builder_instruct(int argc, VALUE *argv, VALUE self) {
Builder b = (Builder)DATA_PTR(self);
i_am_a_child(b, false);
append_indent(b);
if (0 == argc) {
buf_append_string(&b->buf, "<?xml?>", 7);
b->col += 7;
b->pos += 7;
} else {
volatile VALUE v;
buf_append_string(&b->buf, "<?", 2);
b->col += 2;
b->pos += 2;
append_sym_str(b, *argv);
if (1 < argc && rb_cHash == rb_obj_class(argv[1])) {
int len;
if (Qnil != (v = rb_hash_lookup(argv[1], ox_version_sym))) {
if (rb_cString != rb_obj_class(v)) {
rb_raise(ox_parse_error_class, ":version must be a Symbol.\n");
}
len = (int)RSTRING_LEN(v);
buf_append_string(&b->buf, " version=\"", 10);
buf_append_string(&b->buf, StringValuePtr(v), len);
buf_append(&b->buf, '"');
b->col += len + 11;
b->pos += len + 11;
}
if (Qnil != (v = rb_hash_lookup(argv[1], ox_encoding_sym))) {
if (rb_cString != rb_obj_class(v)) {
rb_raise(ox_parse_error_class, ":encoding must be a Symbol.\n");
}
len = (int)RSTRING_LEN(v);
buf_append_string(&b->buf, " encoding=\"", 11);
buf_append_string(&b->buf, StringValuePtr(v), len);
buf_append(&b->buf, '"');
b->col += len + 12;
b->pos += len + 12;
strncpy(b->encoding, StringValuePtr(v), sizeof(b->encoding));
b->encoding[sizeof(b->encoding) - 1] = '\0';
}
if (Qnil != (v = rb_hash_lookup(argv[1], ox_standalone_sym))) {
if (rb_cString != rb_obj_class(v)) {
rb_raise(ox_parse_error_class, ":standalone must be a Symbol.\n");
}
len = (int)RSTRING_LEN(v);
buf_append_string(&b->buf, " standalone=\"", 13);
buf_append_string(&b->buf, StringValuePtr(v), len);
buf_append(&b->buf, '"');
b->col += len + 14;
b->pos += len + 14;
}
}
buf_append_string(&b->buf, "?>", 2);
b->col += 2;
b->pos += 2;
}
return Qnil;
}
Returns the current line in the output. The first line is line 1.
static VALUE
builder_line(VALUE self) {
return LONG2NUM(((Builder)DATA_PTR(self))->line);
}
Closes the current element.
static VALUE
builder_pop(VALUE self) {
pop((Builder)DATA_PTR(self));
return Qnil;
}
Returns the number of bytes written.
static VALUE
builder_pos(VALUE self) {
return LONG2NUM(((Builder)DATA_PTR(self))->pos);
}
Adds the provided string directly to the XML without formatting or modifications.
text - (String) contents to be added
static VALUE
builder_raw(VALUE self, VALUE text) {
Builder b = (Builder)DATA_PTR(self);
volatile VALUE v = text;
const char *str;
const char *s;
const char *end;
int len;
if (T_STRING != rb_type(v)) {
v = rb_funcall(v, ox_to_s_id, 0);
}
str = StringValuePtr(v);
len = (int)RSTRING_LEN(v);
s = str;
end = str + len;
i_am_a_child(b, true);
buf_append_string(&b->buf, str, len);
b->col += len;
s = strchr(s, '\n');
while (NULL != s) {
b->line++;
b->col = end - s;
s = strchr(s + 1, '\n');
}
b->pos += len;
return Qnil;
}
Adds a text element to the XML string being formed.
text - (String) contents of the text field
static VALUE
builder_text(VALUE self, VALUE text) {
Builder b = (Builder)DATA_PTR(self);
volatile VALUE v = text;
if (T_STRING != rb_type(v)) {
v = rb_funcall(v, ox_to_s_id, 0);
}
i_am_a_child(b, true);
append_string(b, StringValuePtr(v), RSTRING_LEN(v), xml_element_chars);
return Qnil;
}
Returns the JSON document string in what ever state the construction is at.
static VALUE
builder_to_s(VALUE self) {
return to_s((Builder)DATA_PTR(self));
}