#include "ruby_libxml.h" #include "ruby_xml_writer.h" VALUE cXMLWriter; static VALUE sEncoding, sStandalone; #ifdef LIBXML_WRITER_ENABLED /* * Document-class: LibXML::XML::Writer * * The XML::Writer class provides a simpler, alternative way to build a valid * XML document from scratch (forward-only) compared to a DOM approach (based * on XML::Document class). * * For a more in depth tutorial, albeit in C, see http://xmlsoft.org/xmlwriter.html */ #include typedef enum { RXMLW_OUTPUT_NONE, RXMLW_OUTPUT_IO, RXMLW_OUTPUT_DOC, RXMLW_OUTPUT_STRING } rxmlw_output_type; typedef struct { VALUE output; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *encoding; #endif /* HAVE_RUBY_ENCODING_H */ xmlBufferPtr buffer; xmlTextWriterPtr writer; rxmlw_output_type output_type; } rxml_writer_object; #ifdef HAVE_RUBY_ENCODING_H #define /*VALUE*/ rxml_writer_c_to_ruby_string(/*const xmlChar **/ string, /*long*/ string_len) \ rb_external_str_new_with_enc(string, string_len, rb_utf8_encoding()) #define /*VALUE*/ rxml_writer_ruby_string_to_utf8(/*VALUE*/ string) \ rb_str_conv_enc(string, rb_enc_get(string), rb_utf8_encoding()) // rb_str_export_to_enc(string, rb_utf8_encoding()) #define /*void*/ rxml_writer_free_utf8_string(/*VALUE*/ orig, /*VALUE*/ utf8) \ do { \ if (orig != utf8) { \ rb_str_free(utf8); \ } \ } while (0); #else #define /*VALUE*/ rxml_writer_c_to_ruby_string(/*const xmlChar **/ string, /*long*/ string_len) \ rb_str_new(string, string_len) #define /*VALUE*/ rxml_writer_ruby_string_to_utf8(/*VALUE*/ string) \ string #define /*void*/ rxml_writer_free_utf8_string(/*VALUE*/ orig, /*VALUE*/ utf8) \ /* NOP */ #endif /* HAVE_RUBY_ENCODING_H */ static void rxml_writer_free(rxml_writer_object *rwo) { #if 0 /* seems to be done by xmlFreeTextWriter */ if (NULL != rwo->buffer) { xmlBufferFree(rwo->buffer); } #endif xmlFreeTextWriter(rwo->writer); xfree(rwo); } static void rxml_writer_mark(rxml_writer_object *rwo) { if (!NIL_P(rwo->output)) { rb_gc_mark(rwo->output); } } static VALUE rxml_writer_wrap(rxml_writer_object *rwo) { return Data_Wrap_Struct(cXMLWriter, rxml_writer_mark, rxml_writer_free, rwo); } static rxml_writer_object *rxml_textwriter_get(VALUE obj) { rxml_writer_object *rwo; Data_Get_Struct(obj, rxml_writer_object, rwo); return rwo; } /* ===== public class methods ===== */ /* call-seq: * XML::Writer::io(io) -> XML::Writer * * Creates a XML::Writer which will write XML directly into an IO object. */ static VALUE rxml_writer_io(VALUE klass, VALUE io) { #if 0 typedef int (*xmlOutputCloseCallback)(void * context); typedef int (*xmlOutputWriteCallback)(void * context, const char * buffer, int len); ssize_t rb_io_bufwrite(VALUE io, const void *buf, size_t size); xmlOutputBufferPtr xmlOutputBufferCreateIO(xmlOutputWriteCallback iowrite, xmlOutputCloseCallback ioclose, void * ioctx, xmlCharEncodingHandlerPtr encoder) xmlCharEncodingHandlerPtr xmlFindCharEncodingHandler(const char * name); #endif xmlOutputBufferPtr out; rxml_writer_object *rwo; rwo = ALLOC(rxml_writer_object); rwo->output = io; rwo->buffer = NULL; #ifdef HAVE_RUBY_ENCODING_H rwo->encoding = NULL; #endif /* HAVE_RUBY_ENCODING_H */ rwo->output_type = RXMLW_OUTPUT_IO; if (NULL == (out = xmlOutputBufferCreateIO(rxml_write_callback, NULL, (void *) io, NULL))) { rxml_raise(&xmlLastError); } if (NULL == (rwo->writer = xmlNewTextWriter(out))) { rxml_raise(&xmlLastError); } return rxml_writer_wrap(rwo); } /* call-seq: * XML::Writer::file(path) -> XML::Writer * * Creates a XML::Writer object which will write XML into the file with * the given name. */ static VALUE rxml_writer_file(VALUE klass, VALUE filename) { rxml_writer_object *rwo; rwo = ALLOC(rxml_writer_object); rwo->output = Qnil; rwo->buffer = NULL; #ifdef HAVE_RUBY_ENCODING_H rwo->encoding = NULL; #endif /* HAVE_RUBY_ENCODING_H */ rwo->output_type = RXMLW_OUTPUT_NONE; if (NULL == (rwo->writer = xmlNewTextWriterFilename(StringValueCStr(filename), 0))) { rxml_raise(&xmlLastError); } return rxml_writer_wrap(rwo); } /* call-seq: * XML::Writer::string -> XML::Writer * * Creates a XML::Writer which will write XML into memory, as string. */ static VALUE rxml_writer_string(VALUE klass) { rxml_writer_object *rwo; rwo = ALLOC(rxml_writer_object); rwo->output = Qnil; #ifdef HAVE_RUBY_ENCODING_H rwo->encoding = NULL; #endif /* HAVE_RUBY_ENCODING_H */ rwo->output_type = RXMLW_OUTPUT_STRING; if (NULL == (rwo->buffer = xmlBufferCreate())) { rxml_raise(&xmlLastError); } if (NULL == (rwo->writer = xmlNewTextWriterMemory(rwo->buffer, 0))) { xmlBufferFree(rwo->buffer); rxml_raise(&xmlLastError); } return rxml_writer_wrap(rwo); } /* call-seq: * XML::Writer::document -> XML::Writer * * Creates a XML::Writer which will write into an in memory XML::Document */ static VALUE rxml_writer_doc(VALUE klass) { xmlDocPtr doc; rxml_writer_object *rwo; rwo = ALLOC(rxml_writer_object); rwo->buffer = NULL; rwo->output = Qnil; #ifdef HAVE_RUBY_ENCODING_H rwo->encoding = NULL; #endif /* HAVE_RUBY_ENCODING_H */ rwo->output_type = RXMLW_OUTPUT_DOC; if (NULL == (rwo->writer = xmlNewTextWriterDoc(&doc, 0))) { rxml_raise(&xmlLastError); } rwo->output = rxml_document_wrap(doc); return rxml_writer_wrap(rwo); } /* ===== public instance methods ===== */ /* call-seq: * writer.flush(empty? = true) -> (num|string) * * Flushes the output buffer. Returns the number of written bytes or * the current content of the internal buffer for a in memory XML::Writer. * If +empty?+ is +true+, and for a in memory XML::Writer, this internel * buffer is empty. */ static VALUE rxml_writer_flush(int argc, VALUE *argv, VALUE self) { int ret; VALUE empty; rxml_writer_object *rwo; rb_scan_args(argc, argv, "01", &empty); rwo = rxml_textwriter_get(self); if (-1 == (ret = xmlTextWriterFlush(rwo->writer))) { rxml_raise(&xmlLastError); } if (NULL != rwo->buffer) { VALUE content; #ifdef HAVE_RUBY_ENCODING_H content = rb_external_str_new_with_enc(rwo->buffer->content, rwo->buffer->use, rwo->encoding); #else content = rb_str_new(rwo->buffer->content, rwo->buffer->use); #endif /* HAVE_RUBY_ENCODING_H */ if (NIL_P(empty) || RTEST(empty)) { /* nil = default value = true */ xmlBufferEmpty(rwo->buffer); } return content; } else { return INT2NUM(ret); } } /* call-seq: * writer.result -> (XML::Document|"string"|nil) * * Returns the associated result object to the XML::Writer creation. * A String for a XML::Writer object created with XML::Writer::string, * a XML::Document with XML::Writer::document, etc. */ static VALUE rxml_writer_result(VALUE self) { VALUE ret; rxml_writer_object *rwo; ret = Qnil; rwo = rxml_textwriter_get(self); if (-1 == (ret = xmlTextWriterFlush(rwo->writer))) { rxml_raise(&xmlLastError); } switch (rwo->output_type) { case RXMLW_OUTPUT_DOC: ret = rwo->output; break; case RXMLW_OUTPUT_STRING: ret = rxml_writer_c_to_ruby_string(rwo->buffer->content, rwo->buffer->use); break; case RXMLW_OUTPUT_IO: case RXMLW_OUTPUT_NONE: break; default: rb_bug("unexpected output"); break; } return ret; } /* ===== private helpers ===== */ static VALUE numeric_rxml_writer_void(VALUE obj, int (*fn)(xmlTextWriterPtr)) { int ret; rxml_writer_object *rwo; rwo = rxml_textwriter_get(obj); ret = fn(rwo->writer); return (-1 == ret ? Qfalse : Qtrue); } #define numeric_rxml_writer_string(/*VALUE*/ obj, /*VALUE*/ name_or_content, /*int (**/fn/*)(xmlTextWriterPtr, const xmlChar *)*/) \ numeric_rxml_writer_va_strings(obj, Qundef, 1, fn, name_or_content) /** * This is quite ugly but thanks to libxml2 coding style, all xmlTextWriter* * calls can be redirected to a single function. This can be convenient to: * - avoid repeating yourself * - convert strings to UTF-8 * - validate names * and so on **/ #define XMLWRITER_MAX_STRING_ARGS 5 static VALUE numeric_rxml_writer_va_strings(VALUE obj, VALUE pe, size_t strings_count, int (*fn)(ANYARGS), ...) { va_list ap; int argc, ret; rxml_writer_object *rwo; const xmlChar *argv[XMLWRITER_MAX_STRING_ARGS]; VALUE utf8[XMLWRITER_MAX_STRING_ARGS], orig[XMLWRITER_MAX_STRING_ARGS]; if (strings_count > XMLWRITER_MAX_STRING_ARGS) { rb_bug("more arguments than expected"); } ret = -1; va_start(ap, fn); rwo = rxml_textwriter_get(obj); for (argc = 0; argc < strings_count; argc++) { VALUE arg; arg = va_arg(ap, VALUE); orig[argc] = arg; if (NIL_P(arg)) { utf8[argc] = Qnil; argv[argc] = NULL; } else { utf8[argc] = rxml_writer_ruby_string_to_utf8(orig[argc]); argv[argc] = BAD_CAST StringValueCStr(utf8[argc]); } } va_end(ap); if (Qundef == pe) { switch (strings_count) { case 0: ret = fn(rwo->writer); break; case 1: ret = fn(rwo->writer, argv[0]); break; case 2: ret = fn(rwo->writer, argv[0], argv[1]); break; case 3: ret = fn(rwo->writer, argv[0], argv[1], argv[2]); break; case 4: ret = fn(rwo->writer, argv[0], argv[1], argv[2], argv[3]); break; case 5: ret = fn(rwo->writer, argv[0], argv[1], argv[2], argv[3], argv[4]); break; default: break; } } else { int xpe; xpe = RTEST(pe); switch (strings_count) { /* strings_count doesn't include pe */ case 0: ret = fn(rwo->writer, xpe); break; case 1: ret = fn(rwo->writer, xpe, argv[0]); break; case 2: ret = fn(rwo->writer, xpe, argv[0], argv[1]); break; case 3: ret = fn(rwo->writer, xpe, argv[0], argv[1], argv[2]); break; case 4: ret = fn(rwo->writer, xpe, argv[0], argv[1], argv[2], argv[3]); break; case 5: ret = fn(rwo->writer, xpe, argv[0], argv[1], argv[2], argv[3], argv[4]); break; default: break; } } #ifdef HAVE_RUBY_ENCODING_H while (--strings_count > 0) { if (!NIL_P(orig[strings_count])) { rxml_writer_free_utf8_string(orig[strings_count], utf8[strings_count]); } } #endif /* HAVE_RUBY_ENCODING_H */ return (-1 == ret ? Qfalse : Qtrue); } /* ===== public instance methods ===== */ #if LIBXML_VERSION >= 20605 /* call-seq: * writer.set_indent(indentation) -> (true|false) * * Toggles indentation on or off. Returns +false+ on failure. * * Availability: libxml2 >= 2.6.5 */ static VALUE rxml_writer_set_indent(VALUE self, VALUE indentation) { int ret; rxml_writer_object *rwo; rwo = rxml_textwriter_get(self); ret = xmlTextWriterSetIndent(rwo->writer, RTEST(indentation)); return (-1 == ret ? Qfalse : Qtrue); } /* call-seq: * writer.set_indent_string(string) -> (true|false) * * Sets the string to use to indent each element of the document. * Don't forget to enable indentation with set_indent. Returns * +false+ on failure. * * Availability: libxml2 >= 2.6.5 */ static VALUE rxml_writer_set_indent_string(VALUE self, VALUE indentation) { return numeric_rxml_writer_string(self, indentation, xmlTextWriterSetIndentString); } #endif /* LIBXML_VERSION >= 20605 */ /* ===== public full tag interface ===== */ /* write_ = start_ + write_string + end_ */ /* call-seq: * writer.write_comment(content) -> (true|false) * * Writes a full comment tag, all at once. Returns +false+ on failure. * This is equivalent to start_comment + write_string(content) + end_comment. */ static VALUE rxml_writer_write_comment(VALUE self, VALUE content) { return numeric_rxml_writer_string(self, content, xmlTextWriterWriteComment); } /* call-seq: * writer.write_cdata(content) -> (true|false) * * Writes a full CDATA section, all at once. Returns +false+ on failure. * This is equivalent to start_cdata + write_string(content) + end_cdata. */ static VALUE rxml_writer_write_cdata(VALUE self, VALUE content) { return numeric_rxml_writer_string(self, content, xmlTextWriterWriteCDATA); } static VALUE rxml_writer_start_element(VALUE, VALUE); static VALUE rxml_writer_start_element_ns(int, VALUE *, VALUE); static VALUE rxml_writer_end_element(VALUE); /* call-seq: * writer.write_element(name, content) -> (true|false) * * Writes a full element tag, all at once. Returns +false+ on failure. * This is equivalent to start_element(name) + write_string(content) + * end_element. */ static VALUE rxml_writer_write_element(int argc, VALUE *argv, VALUE self) { VALUE name, content; rb_scan_args(argc, argv, "11", &name, &content); if (Qnil == content) { if (Qfalse == rxml_writer_start_element(self, name)) { return Qfalse; } return rxml_writer_end_element(self); } else { return numeric_rxml_writer_va_strings(self, Qundef, 2, xmlTextWriterWriteElement, name, content); } } #define ARRAY_SIZE(array) \ (sizeof(array) / sizeof((array)[0])) /* call-seq: * writer.write_element_ns(prefix, name, namespaceURI, content) -> (true|false) * * Writes a full namespaced element tag, all at once. Returns +false+ on failure. * This is a shortcut for start_element_ns(prefix, name, namespaceURI) + * write_string(content) + end_element. * * Notes: * - by default, the xmlns: definition is repeated on every element. If you want * the prefix, but don't want the xmlns: declaration repeated, set +namespaceURI+ * to nil or omit it. Don't forget to declare the namespace prefix somewhere * earlier. * - +content+ can be omitted for an empty tag */ static VALUE rxml_writer_write_element_ns(int argc, VALUE *argv, VALUE self) { VALUE prefix, name, namespaceURI, content; rb_scan_args(argc, argv, "22", &prefix, &name, &namespaceURI, &content); if (Qnil == content) { VALUE argv[3] = { prefix, name, namespaceURI }; if (Qfalse == rxml_writer_start_element_ns(ARRAY_SIZE(argv), argv, self)) { return Qfalse; } return rxml_writer_end_element(self); } else { return numeric_rxml_writer_va_strings(self, Qundef, 4, xmlTextWriterWriteElementNS, prefix, name, namespaceURI, content); } } /* call-seq: * writer.write_attribute(name, content) -> (true|false) * * Writes a full attribute, all at once. Returns +false+ on failure. * Same as start_attribute(name) + write_string(content) + end_attribute. */ static VALUE rxml_writer_write_attribute(VALUE self, VALUE name, VALUE content) { return numeric_rxml_writer_va_strings(self, Qundef, 2, xmlTextWriterWriteAttribute, name, content); } /* call-seq: * writer.write_attribute_ns(prefix, name, namespaceURI, content) -> (true|false) * * Writes a full namespaced attribute, all at once. Returns +false+ on failure. * Same as start_attribute_ns(prefix, name, namespaceURI) + * write_string(content) + end_attribute. * * Notes: * - by default, the xmlns: definition is repeated on every element. If you want * the prefix, but don't want the xmlns: declaration repeated, set +namespaceURI+ * to nil or omit it. Don't forget to declare the namespace prefix somewhere * earlier. * - +content+ can be omitted too for an empty attribute */ static VALUE rxml_writer_write_attribute_ns(int argc, VALUE *argv, VALUE self) { VALUE prefix, name, namespaceURI, content; rb_scan_args(argc, argv, "22", &prefix, &name, &namespaceURI, &content); return numeric_rxml_writer_va_strings(self, Qundef, 4, xmlTextWriterWriteAttributeNS, prefix, name, namespaceURI, content); } /* call-seq: * writer.write_pi(target, content) -> (true|false) * * Writes a full CDATA tag, all at once. Returns +false+ on failure. * This is a shortcut for start_pi(target) + write_string(content) + end_pi. */ static VALUE rxml_writer_write_pi(VALUE self, VALUE target, VALUE content) { return numeric_rxml_writer_va_strings(self, Qundef, 2, xmlTextWriterWritePI, target, content); } /* ===== public start/end interface ===== */ /* call-seq: * writer.write_string(content) -> (true|false) * * Safely (problematic characters are internally translated to their * associated named entities) writes a string into the current node * (attribute, element, comment, ...). Returns +false+ on failure. */ static VALUE rxml_writer_write_string(VALUE self, VALUE content) { return numeric_rxml_writer_string(self, content, xmlTextWriterWriteString); } /* call-seq: * writer.write_raw(content) -> (true|false) * * Writes the string +content+ as is, reserved characters are not * translated to their associated entities. Returns +false+ on failure. * Consider write_string to handle them. */ static VALUE rxml_writer_write_raw(VALUE self, VALUE content) { return numeric_rxml_writer_string(self, content, xmlTextWriterWriteRaw); } /* call-seq: * writer.start_attribute(name) -> (true|false) * * Starts an attribute. Returns +false+ on failure. */ static VALUE rxml_writer_start_attribute(VALUE self, VALUE name) { return numeric_rxml_writer_string(self, name, xmlTextWriterStartAttribute); } /* call-seq: * writer.start_attribute_ns(prefix, name, namespaceURI) -> (true|false) * * Starts a namespaced attribute. Returns +false+ on failure. * * Note: by default, the xmlns: definition is repeated on every element. If * you want the prefix, but don't want the xmlns: declaration repeated, set * +namespaceURI+ to nil or omit it. Don't forget to declare the namespace * prefix somewhere earlier. */ static VALUE rxml_writer_start_attribute_ns(int argc, VALUE *argv, VALUE self) { VALUE prefix, name, namespaceURI; rb_scan_args(argc, argv, "21", &prefix, &name, &namespaceURI); return numeric_rxml_writer_va_strings(self, Qundef, 3, xmlTextWriterStartAttributeNS, prefix, name, namespaceURI); } /* call-seq: * writer.end_attribute -> (true|false) * * Ends an attribute, namespaced or not. Returns +false+ on failure. */ static VALUE rxml_writer_end_attribute(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterEndAttribute); } #if LIBXML_VERSION >= 20607 /* call-seq: * writer.start_comment -> (true|false) * * Starts a comment. Returns +false+ on failure. * Note: libxml2 >= 2.6.7 required */ static VALUE rxml_writer_start_comment(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterStartComment); } /* call-seq: * writer.end_comment -> (true|false) * * Ends current comment, returns +false+ on failure. * Note: libxml2 >= 2.6.7 required */ static VALUE rxml_writer_end_comment(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterEndComment); } #endif /* LIBXML_VERSION >= 20607 */ /* call-seq: * writer.start_element(name) -> (true|false) * * Starts a new element. Returns +false+ on failure. */ static VALUE rxml_writer_start_element(VALUE self, VALUE name) { return numeric_rxml_writer_string(self, name, xmlTextWriterStartElement); } /* call-seq: * writer.start_element_ns(prefix, name, namespaceURI) -> (true|false) * * Starts a new namespaced element. Returns +false+ on failure. * * Note: by default, the xmlns: definition is repeated on every element. If * you want the prefix, but don't want the xmlns: declaration repeated, set * +namespaceURI+ to nil or omit it. Don't forget to declare the namespace * prefix somewhere earlier. */ static VALUE rxml_writer_start_element_ns(int argc, VALUE *argv, VALUE self) { VALUE prefix, name, namespaceURI; rb_scan_args(argc, argv, "21", &prefix, &name, &namespaceURI); return numeric_rxml_writer_va_strings(self, Qundef, 3, xmlTextWriterStartElementNS, prefix, name, namespaceURI); } /* call-seq: * writer.end_element -> (true|false) * * Ends current element, namespaced or not. Returns +false+ on failure. */ static VALUE rxml_writer_end_element(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterEndElement); } /* call-seq: * writer.write_full_end_element -> (true|false) * * Ends current element, namespaced or not. Returns +false+ on failure. * This method writes an end tag even if the element is empty (), * end_element does not (). */ static VALUE rxml_writer_full_end_element(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterFullEndElement); } /* call-seq: * writer.start_cdata -> (true|false) * * Starts a new CDATA section. Returns +false+ on failure. */ static VALUE rxml_writer_start_cdata(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterStartCDATA); } /* call-seq: * writer.end_cdata -> (true|false) * * Ends current CDATA section. Returns +false+ on failure. */ static VALUE rxml_writer_end_cdata(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterEndCDATA); } /* call-seq: * writer.start_document -> (true|false) * writer.start_document(:encoding => XML::Encoding::UTF_8, * :standalone => true) -> (true|false) * * Starts a new document. Returns +false+ on failure. * * You may provide an optional hash table to control XML header that will be * generated. Valid options are: * - encoding: the output document encoding, defaults to nil (= UTF-8). Valid * values are the encoding constants defined on XML::Encoding * - standalone: nil (default) or a boolean to indicate if the document is * standalone or not */ static VALUE rxml_writer_start_document(int argc, VALUE *argv, VALUE self) { int ret; VALUE options; rxml_writer_object *rwo; const char *xencoding, *xstandalone; options = Qnil; xstandalone = xencoding = NULL; rb_scan_args(argc, argv, "01", &options); if (!NIL_P(options)) { VALUE encoding, standalone; encoding = standalone = Qnil; Check_Type(options, T_HASH); encoding = rb_hash_aref(options, sEncoding); xencoding = NIL_P(encoding) ? NULL : xmlGetCharEncodingName(NUM2INT(encoding)); standalone = rb_hash_aref(options, sStandalone); if (NIL_P(standalone)) { xstandalone = NULL; } else { xstandalone = RTEST(standalone) ? "yes" : "no"; } } rwo = rxml_textwriter_get(self); #ifdef HAVE_RUBY_ENCODING_H rwo->encoding = rxml_figure_encoding(xencoding); #endif /* !HAVE_RUBY_ENCODING_H */ ret = xmlTextWriterStartDocument(rwo->writer, NULL, xencoding, xstandalone); return (-1 == ret ? Qfalse : Qtrue); } /* call-seq: * writer.end_document -> (true|false) * * Ends current document. Returns +false+ on failure. */ static VALUE rxml_writer_end_document(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterEndDocument); } /* call-seq: * writer.start_pi(target) -> (true|false) * * Starts a new processing instruction. Returns +false+ on failure. */ static VALUE rxml_writer_start_pi(VALUE self, VALUE target) { return numeric_rxml_writer_string(self, target, xmlTextWriterStartPI); } /* call-seq: * writer.end_pi -> (true|false) * * Ends current processing instruction. Returns +false+ on failure. */ static VALUE rxml_writer_end_pi(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterEndPI); } /* call-seq: * writer.start_dtd(qualifiedName, publicId, systemId) -> (true|false) * * Starts a DTD. Returns +false+ on failure. */ static VALUE rxml_writer_start_dtd(int argc, VALUE *argv, VALUE self) { VALUE name, pubid, sysid; rb_scan_args(argc, argv, "12", &name, &pubid, &sysid); return numeric_rxml_writer_va_strings(self, Qundef, 3, xmlTextWriterStartDTD, name, pubid, sysid); } /* call-seq: * writer.start_dtd_element(qualifiedName) -> (true|false) * * Starts a DTD element (). Returns +false+ on failure. */ static VALUE rxml_writer_start_dtd_element(VALUE self, VALUE name) { return numeric_rxml_writer_string(self, name, xmlTextWriterStartDTDElement); } /* call-seq: * writer.start_dtd_entity(name, pe = false) -> (true|false) * * Starts a DTD entity (). Returns +false+ on failure. */ static VALUE rxml_writer_start_dtd_entity(int argc, VALUE *argv, VALUE self) { VALUE name, pe; rb_scan_args(argc, argv, "11", &name, &pe); if (NIL_P(pe)) { pe = Qfalse; } return numeric_rxml_writer_va_strings(self, pe, 1, xmlTextWriterStartDTDEntity, name); } /* call-seq: * writer.start_dtd_attlist(name) -> (true|false) * * Starts a DTD attribute list (). Returns +false+ on failure. */ static VALUE rxml_writer_start_dtd_attlist(VALUE self, VALUE name) { return numeric_rxml_writer_string(self, name, xmlTextWriterStartDTDAttlist); } /* call-seq: * writer.end_dtd -> (true|false) * * Ends current DTD, returns +false+ on failure. */ static VALUE rxml_writer_end_dtd(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterEndDTD); } /* call-seq: * writer.end_dtd_entity -> (true|false) * * Ends current DTD entity, returns +false+ on failure. */ static VALUE rxml_writer_end_dtd_entity(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterEndDTDEntity); } /* call-seq: * writer.end_dtd_attlist -> (true|false) * * Ends current DTD attribute list, returns +false+ on failure. */ static VALUE rxml_writer_end_dtd_attlist(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterEndDTDAttlist); } /* call-seq: * writer.end_dtd_element -> (true|false) * * Ends current DTD element, returns +false+ on failure. */ static VALUE rxml_writer_end_dtd_element(VALUE self) { return numeric_rxml_writer_void(self, xmlTextWriterEndDTDElement); } /* call-seq: * writer.write_dtd(name [ [ [, publicId ], systemId ], subset ]) -> (true|false) * * Writes a DTD, all at once. Returns +false+ on failure. * - name: dtd name * - publicId: external subset public identifier, use nil for a SYSTEM doctype * - systemId: external subset system identifier * - subset: content * * Examples: * writer.write_dtd 'html' * #=> * writer.write_dtd 'docbook', nil, 'http://www.docbook.org/xml/5.0/dtd/docbook.dtd' * #=> * writer.write_dtd 'html', '-//W3C//DTD XHTML 1.1//EN', 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd' * #=> * writer.write_dtd 'person', nil, nil, '' * #=> ]> */ static VALUE rxml_writer_write_dtd(int argc, VALUE *argv, VALUE self) { VALUE name, pubid, sysid, subset; rb_scan_args(argc, argv, "13", &name, &pubid, &sysid, &subset); return numeric_rxml_writer_va_strings(self, Qundef, 4, xmlTextWriterWriteDTD, name, pubid, sysid, subset); } /* call-seq: * writer.write_dtd_attlist(name, content) -> (true|false) * * Writes a DTD attribute list, all at once. Returns +false+ on failure. * writer.write_dtd_attlist 'id', 'ID #IMPLIED' * #=> */ static VALUE rxml_writer_write_dtd_attlist(VALUE self, VALUE name, VALUE content) { return numeric_rxml_writer_va_strings(self, Qundef, 2, xmlTextWriterWriteDTDAttlist, name, content); } /* call-seq: * writer.write_dtd_element(name, content) -> (true|false) * * Writes a full DTD element, all at once. Returns +false+ on failure. * writer.write_dtd_element 'person', '(firstname,lastname)' * #=> */ static VALUE rxml_writer_write_dtd_element(VALUE self, VALUE name, VALUE content) { return numeric_rxml_writer_va_strings(self, Qundef, 2, xmlTextWriterWriteDTDElement, name, content); } /* call-seq: * writer.write_dtd_entity(name, publicId, systemId, ndataid, content, pe) -> (true|false) * * Writes a DTD entity, all at once. Returns +false+ on failure. */ static VALUE rxml_writer_write_dtd_entity(VALUE self, VALUE name, VALUE pubid, VALUE sysid, VALUE ndataid, VALUE content, VALUE pe) { return numeric_rxml_writer_va_strings(self, pe, 5, xmlTextWriterWriteDTDEntity, name, pubid, sysid, ndataid, content); } /* call-seq: * writer.write_dtd_external_entity(name, publicId, systemId, ndataid, pe) -> (true|false) * * Writes a DTD external entity. The entity must have been started * with start_dtd_entity. Returns +false+ on failure. * - name: the name of the DTD entity * - publicId: the public identifier, which is an alternative to the system identifier * - systemId: the system identifier, which is the URI of the DTD * - ndataid: the xml notation name * - pe: +true+ if this is a parameter entity (to be used only in the DTD * itself), +false+ if not */ static VALUE rxml_writer_write_dtd_external_entity(VALUE self, VALUE name, VALUE pubid, VALUE sysid, VALUE ndataid, VALUE pe) { return numeric_rxml_writer_va_strings(self, pe, 4, xmlTextWriterWriteDTDExternalEntity, name, pubid, sysid, ndataid); } /* call-seq: * writer.write_dtd_external_entity_contents(publicId, systemId, ndataid) -> (true|false) * * Writes the contents of a DTD external entity, all at once. Returns +false+ on failure. */ static VALUE rxml_writer_write_dtd_external_entity_contents(VALUE self, VALUE pubid, VALUE sysid, VALUE ndataid) { return numeric_rxml_writer_va_strings(self, Qundef, 3, xmlTextWriterWriteDTDExternalEntityContents, pubid, sysid, ndataid); } /* call-seq: * writer.write_dtd_internal_entity(name, content, pe) -> (true|false) * * Writes a DTD internal entity, all at once. Returns +false+ on failure. * * Examples: * writer.write_dtd_entity 'Shape', '(rect|circle|poly|default)', true * #=> * writer.write_dtd_entity 'delta', 'δ', false * #=> */ static VALUE rxml_writer_write_dtd_internal_entity(VALUE self, VALUE name, VALUE content, VALUE pe) { return numeric_rxml_writer_va_strings(self, pe, 2, xmlTextWriterWriteDTDInternalEntity, name, content); } /* call-seq: * writer.write_dtd_notation(name, publicId, systemId) -> (true|false) * * Writes a DTD entity, all at once. Returns +false+ on failure. */ static VALUE rxml_writer_write_dtd_notation(VALUE self, VALUE name, VALUE pubid, VALUE sysid) { return numeric_rxml_writer_va_strings(self, Qundef, 3, xmlTextWriterWriteDTDNotation, name, pubid, sysid); } #if LIBXML_VERSION >= 20900 /* call-seq: * writer.set_quote_char(...) -> (true|false) * * Sets the character used to quote attributes. Returns +false+ on failure. * * Notes: * - only " (default) and ' characters are valid * - availability: libxml2 >= 2.9.0 */ static VALUE rxml_writer_set_quote_char(VALUE self, VALUE quotechar) { int ret; const char *xquotechar; rxml_writer_object *rwo; rwo = rxml_textwriter_get(self); xquotechar = StringValueCStr(quotechar); ret = xmlTextWriterSetQuoteChar(rwo->writer, (xmlChar) xquotechar[0]); return (-1 == ret ? Qfalse : Qtrue); } #endif /* LIBXML_VERSION >= 20900 */ #endif /* LIBXML_WRITER_ENABLED */ /* grep -P 'xmlTextWriter(Start|End|Write)(?!DTD|V?Format)[^(]+' /usr/include/libxml2/libxml/xmlwriter.h */ void rxml_init_writer(void) { sEncoding = ID2SYM(rb_intern("encoding")); sStandalone = ID2SYM(rb_intern("standalone")); cXMLWriter = rb_define_class_under(mXML, "Writer", rb_cObject); #ifdef LIBXML_WRITER_ENABLED rb_define_singleton_method(cXMLWriter, "io", rxml_writer_io, 1); rb_define_singleton_method(cXMLWriter, "file", rxml_writer_file, 1); rb_define_singleton_method(cXMLWriter, "document", rxml_writer_doc, 0); rb_define_singleton_method(cXMLWriter, "string", rxml_writer_string, 0); /* misc */ #if LIBXML_VERSION >= 20605 rb_define_method(cXMLWriter, "set_indent", rxml_writer_set_indent, 1); rb_define_method(cXMLWriter, "set_indent_string", rxml_writer_set_indent_string, 1); #endif /* LIBXML_VERSION >= 20605 */ #if LIBXML_VERSION >= 20900 rb_define_method(cXMLWriter, "set_quote_char", rxml_writer_set_quote_char, 1); #endif /* LIBXML_VERSION >= 20900 */ rb_define_method(cXMLWriter, "flush", rxml_writer_flush, -1); rb_define_method(cXMLWriter, "start_dtd", rxml_writer_start_dtd, -1); rb_define_method(cXMLWriter, "start_dtd_entity", rxml_writer_start_dtd_entity, -1); rb_define_method(cXMLWriter, "start_dtd_attlist", rxml_writer_start_dtd_attlist, 1); rb_define_method(cXMLWriter, "start_dtd_element", rxml_writer_start_dtd_element, 1); rb_define_method(cXMLWriter, "write_dtd", rxml_writer_write_dtd, -1); rb_define_method(cXMLWriter, "write_dtd_attlist", rxml_writer_write_dtd_attlist, 2); rb_define_method(cXMLWriter, "write_dtd_element", rxml_writer_write_dtd_element, 2); rb_define_method(cXMLWriter, "write_dtd_entity", rxml_writer_write_dtd_entity, 6); rb_define_method(cXMLWriter, "write_dtd_external_entity", rxml_writer_write_dtd_external_entity, 5); rb_define_method(cXMLWriter, "write_dtd_external_entity_contents", rxml_writer_write_dtd_external_entity_contents, 3); rb_define_method(cXMLWriter, "write_dtd_internal_entity", rxml_writer_write_dtd_internal_entity, 3); rb_define_method(cXMLWriter, "write_dtd_notation", rxml_writer_write_dtd_notation, 3); rb_define_method(cXMLWriter, "end_dtd", rxml_writer_end_dtd, 0); rb_define_method(cXMLWriter, "end_dtd_entity", rxml_writer_end_dtd_entity, 0); rb_define_method(cXMLWriter, "end_dtd_attlist", rxml_writer_end_dtd_attlist, 0); rb_define_method(cXMLWriter, "end_dtd_element", rxml_writer_end_dtd_element, 0); /* tag by parts */ rb_define_method(cXMLWriter, "write_raw", rxml_writer_write_raw, 1); rb_define_method(cXMLWriter, "write_string", rxml_writer_write_string, 1); rb_define_method(cXMLWriter, "start_cdata", rxml_writer_start_cdata, 0); rb_define_method(cXMLWriter, "end_cdata", rxml_writer_end_cdata, 0); rb_define_method(cXMLWriter, "start_attribute", rxml_writer_start_attribute, 1); rb_define_method(cXMLWriter, "start_attribute_ns", rxml_writer_start_attribute_ns, -1); rb_define_method(cXMLWriter, "end_attribute", rxml_writer_end_attribute, 0); rb_define_method(cXMLWriter, "start_element", rxml_writer_start_element, 1); rb_define_method(cXMLWriter, "start_element_ns", rxml_writer_start_element_ns, -1); rb_define_method(cXMLWriter, "end_element", rxml_writer_end_element, 0); rb_define_method(cXMLWriter, "full_end_element", rxml_writer_full_end_element, 0); rb_define_method(cXMLWriter, "start_document", rxml_writer_start_document, -1); rb_define_method(cXMLWriter, "end_document", rxml_writer_end_document, 0); #if LIBXML_VERSION >= 20607 rb_define_method(cXMLWriter, "start_comment", rxml_writer_start_comment, 0); rb_define_method(cXMLWriter, "end_comment", rxml_writer_end_comment, 0); #endif /* LIBXML_VERSION >= 20607 */ rb_define_method(cXMLWriter, "start_pi", rxml_writer_start_pi, 1); rb_define_method(cXMLWriter, "end_pi", rxml_writer_end_pi, 0); /* full tag at once */ rb_define_method(cXMLWriter, "write_attribute", rxml_writer_write_attribute, 2); rb_define_method(cXMLWriter, "write_attribute_ns", rxml_writer_write_attribute_ns, -1); rb_define_method(cXMLWriter, "write_comment", rxml_writer_write_comment, 1); rb_define_method(cXMLWriter, "write_cdata", rxml_writer_write_cdata, 1); rb_define_method(cXMLWriter, "write_element", rxml_writer_write_element, -1); rb_define_method(cXMLWriter, "write_element_ns", rxml_writer_write_element_ns, -1); rb_define_method(cXMLWriter, "write_pi", rxml_writer_write_pi, 2); rb_define_method(cXMLWriter, "result", rxml_writer_result, 0); rb_undef_method(CLASS_OF(cXMLWriter), "new"); #endif }