root/ext/fastxml_doc.c

Revision 2168c40635a237e84a69b74bb34c24f230052ae9, 9.1 KB (checked in by Mark Guzman <segfault@…>, 4 months ago)

finally loading properly as a gem

  • Property mode set to 100644
Line 
1/*
2 * Document-class: FastXml::Doc
3 * Wraps a libxml xml document in memory, providing methods
4 * to modify and extract data from the document.
5 *
6 * Example:
7 *  doc = FastXml( docfile ) # parse the docfile into memory
8 *  puts "first node"
9 *  puts "name = value (text node inside of the element)"
10 *  puts "%s = %s" % [ doc.children.first.name, doc.children.first.content ]
11 *  doc.children.each do |node|
12 *      puts node.inspect
13 *  end
14 */
15// Please see the LICENSE file for copyright, licensing and distribution information
16
17#include "fastxml.h"
18#include "fastxml_node.h"
19#include "fastxml_doc.h"
20#include "fastxml_nodelist.h"
21
22/* {{{ fastxml_doc
23 */
24void Init_fastxml_doc()
25{
26        #ifdef RDOC_SHOULD_BE_SMARTER__THIS_IS_NEVER_RUN
27    rb_mFastXml = rb_define_module( "FastXml" );
28        #endif
29        rb_cFastXmlDoc = rb_define_class_under( rb_mFastXml, "Doc", rb_cObject ); 
30       
31        rb_define_method( rb_cFastXmlDoc, "initialize", fastxml_doc_initialize, -1 );
32    rb_define_method( rb_cFastXmlDoc, "search", fastxml_doc_search, 1 );
33    rb_define_method( rb_cFastXmlDoc, "to_s", fastxml_doc_to_s, 0 );
34    rb_define_method( rb_cFastXmlDoc, "root", fastxml_doc_root, 0 ); 
35        rb_define_method( rb_cFastXmlDoc, "transform", fastxml_doc_transform, 1 );
36        rb_define_method( rb_cFastXmlDoc, "children", fastxml_doc_children, 0 );       
37        rb_define_method( rb_cFastXmlDoc, "inspect", fastxml_doc_inspect, 0 );
38        rb_define_method( rb_cFastXmlDoc, "encoding", fastxml_doc_encoding, 0 );
39        rb_define_method( rb_cFastXmlDoc, "encoding=", fastxml_doc_encoding_set, 1 );   
40}
41
42
43/* Returns a friendly summary of the doc
44 *
45 */
46VALUE fastxml_doc_inspect(VALUE self)
47{
48    VALUE *argv;
49    argv = ALLOCA_N( VALUE, 3 );
50    argv[0] = rb_str_new2( "#<%s:0x%x %s>" );
51    argv[1] = CLASS_OF( self );
52    argv[2] = rb_obj_id( self );
53        argv[3] = fastxml_doc_to_s( self );
54
55    return rb_f_sprintf( 4, argv );
56}
57
58/* Returns an Array containing FastXml::Node representations
59 * of the child elements of the doc. They are provided in the order they are found.
60 */ 
61VALUE fastxml_doc_children(VALUE self)
62{
63        VALUE dv;
64    fxml_data_t *data;
65
66    dv = rb_iv_get( self, "@lxml_doc" );   
67    Data_Get_Struct( dv, fxml_data_t, data );
68
69        if (data->doc->children == NULL)
70                return Qnil;
71       
72        return fastxml_nodelist_to_obj( data->doc->children, -1 );
73}
74
75/* Applys an XSLT to the target FastXml::Doc.
76 * Returns the resulting FastXml::Doc, which may not be well-formed xml
77 *
78 * call-seq:
79 *   result = doc.transform FastXml::Doc.new( open( 'my.xslt' ) )
80 *   puts result.to_s 
81 */
82VALUE fastxml_doc_transform(VALUE self, VALUE xform)
83{
84        VALUE ret, dv, xform_dv, ret_str, ret_dv;
85        fxml_data_t *my_data, *xf_data, *ret_data;
86        xmlDocPtr ret_doc;
87        xsltStylesheetPtr style;
88
89        if (xform == Qnil)
90                return Qnil;
91       
92    dv = rb_iv_get( self, "@lxml_doc" );   
93    Data_Get_Struct( dv, fxml_data_t, my_data );
94        xform_dv = rb_iv_get( xform, "@lxml_doc" );
95        Data_Get_Struct( xform_dv, fxml_data_t, xf_data );
96       
97        if (xf_data->doc == NULL)
98                return Qnil;
99               
100        if (xf_data->xslt == NULL) {
101                style = xsltParseStylesheetDoc( xf_data->doc );
102                if (style == NULL) 
103                        return Qnil; // TODO: this should throw a FastXml exception
104        }
105
106        ret_doc = (xmlDocPtr)xsltApplyStylesheet( style, my_data->doc, NULL );
107        ret_str = rb_str_new2( "<shouldNeverBeSeen/>" );
108        ret = rb_class_new_instance( 1, &ret_str, rb_cFastXmlDoc ); // provide an xml snipped temporarily
109        ret_dv = rb_iv_get( ret, "@lxml_doc" );
110        Data_Get_Struct( ret_dv, fxml_data_t, ret_data ); // replace the associated doc with the new one from the transform
111        xmlFree( ret_data->doc );
112        ret_data->doc = ret_doc;
113       
114       
115        return ret;
116}
117
118/* Evaluates an xpath query and returns a list of nodes
119 * that match.
120 *
121 * call-seq:
122 *   doc.search( "//nodes" ).each { |n| puts n.inspect }
123 */
124VALUE fastxml_doc_search(VALUE self, VALUE raw_xpath, VALUE blk)
125{
126        return fastxml_xpath_search( self, raw_xpath, blk );
127}
128
129/* Returns the string representation of the xml document.
130 * Basically a wrapper around xmlDocDumpFormatMemory
131 *
132 * call-seq:
133 *   puts doc.to_s
134 */
135VALUE fastxml_doc_to_s(VALUE self)
136{
137    VALUE ret, dv;
138    xmlChar *xs;
139    fxml_data_t *data;
140    int xs_len;
141
142    dv = rb_iv_get( self, "@lxml_doc" );
143    Data_Get_Struct( dv, fxml_data_t, data );
144
145    xmlDocDumpFormatMemory( data->doc, &xs, &xs_len, 0 );
146
147    ret = rb_str_new( (const char*)xs, xs_len );
148    xmlFree( xs );
149
150    return ret; 
151}
152
153/* Return the xml document's encoding name
154 *
155 * call-seq:
156 *   puts doc.encoding
157 */
158VALUE fastxml_doc_encoding(VALUE self)
159{
160    VALUE dv;
161    fxml_data_t *data;
162
163    dv = rb_iv_get( self, "@lxml_doc" );
164    Data_Get_Struct( dv, fxml_data_t, data );
165
166        if (data->doc->encoding == NULL)
167            return Qnil;
168         
169        return rb_str_new2( (const char*)data->doc->encoding );
170}
171
172/* Set the xml document's encoding name
173 *
174 * call-seq:
175 *   doc.encoding = "UTF-8"
176 */
177VALUE fastxml_doc_encoding_set(VALUE self, VALUE newenc)
178{
179    VALUE dv, strenc;
180    fxml_data_t *data;
181
182    dv = rb_iv_get( self, "@lxml_doc" );
183    Data_Get_Struct( dv, fxml_data_t, data );
184
185        if (newenc == Qnil) {
186                data->doc->encoding = NULL;
187                return newenc;
188        }
189       
190        strenc = newenc;
191        if (rb_respond_to( newenc, s_to_s ))
192                strenc = rb_funcall( newenc, s_to_s, 0 );
193       
194        data->doc->encoding = xmlStrdup( (xmlChar*)RSTRING_PTR(strenc) );
195        return newenc;
196}
197
198
199/* Returns the FastXml::Node object representing the root element of
200 * the target document
201 *
202 * call-seq:
203 *   puts doc.root.name
204 */
205VALUE fastxml_doc_root(VALUE self)
206{
207    VALUE dv;
208    fxml_data_t *data;
209    xmlNodePtr root;
210
211    dv = rb_iv_get( self, "@lxml_doc" );
212    Data_Get_Struct( dv, fxml_data_t, data );
213
214    root = xmlDocGetRootElement( data->doc );
215
216    return fastxml_raw_node_to_obj( root );
217}
218
219/* Returns the FastXml::Node object representing the root element of
220 * the target document
221 *
222 * call-seq:
223 *   puts doc.root.name
224 */
225VALUE fastxml_doc_root_set(VALUE self, VALUE newroot)
226{
227    VALUE dv, odv;
228    fxml_data_t *data, *node_data;
229    xmlNodePtr root, new_root;
230
231    dv = rb_iv_get( self, "@lxml_doc" );
232    Data_Get_Struct( dv, fxml_data_t, data );
233
234        odv = rb_iv_get( self, "@lxml_doc" );
235        Data_Get_Struct( odv, fxml_data_t, node_data );
236
237    root = xmlDocGetRootElement( data->doc );
238       
239        if (rb_obj_is_kind_of(newroot, rb_cFastXmlNode) == Qfalse && rb_obj_is_kind_of(newroot, rb_cString) == Qfalse)
240          rb_raise(rb_eTypeError, "must pass a FastXml::Node or String type object");
241
242        new_root = xmlDocSetRootElement( data->doc, node_data->node );
243        if (new_root == NULL)
244          return Qnil;
245       
246        return newroot;
247}
248
249/* Parse an input string/array/stringio object into a FastXml::Doc object.
250 *
251 * call-seq:
252 *   doc = FastXml::Doc.new( open( "test.xml" ) )
253 *   doc = FastXml::Doc.new( open( "test.xml" ).readlines )
254 *   doc = FastXml::Doc.new( "<test><node>taco</node></test>" )
255 *   doc = FastXml::Doc.new( open( "test.xml" ), { :forgiving => true } ) # turn on the forgiving/liberal libxml parser
256 *   doc = FastXml::Doc.new( open( "test.xml" ), { :validate_dtd => true } ) # turn on strict dtd parsing and loading, invalid xml will cause an exception.
257 *   FastXml::Doc.new( open( "test.xml" ) ) do |doc|
258 *     doc.children.each { |child_node| puts child_node.name }
259 *   end
260 */
261VALUE fastxml_doc_initialize(int argc, VALUE* argv, VALUE self)
262{
263    VALUE data_s, dv, lines, xml_doc_str, opts, blk;
264    fxml_data_t *data;
265    int parser_opts = XML_PARSE_NOERROR | XML_PARSE_NOWARNING;
266    short html_parser = 0;
267
268    if (rb_scan_args( argc, argv, "11&", &xml_doc_str, &opts, &blk ) == 0)
269        return Qnil; // error state
270
271    if (NIL_P(xml_doc_str)) {
272        rb_raise(rb_eArgError, "nil passed as xml document");
273        return Qnil;
274    }
275
276    if (opts != Qnil) {
277        if (rb_hash_aref(opts, rb_sValidateDtd) == Qtrue) {
278            parser_opts = parser_opts | XML_PARSE_DTDLOAD | XML_PARSE_DTDATTR | XML_PARSE_DTDVALID;
279                        rb_iv_set( self, "@validate_dtd", Qtrue );
280        }
281
282        if (rb_hash_aref(opts, rb_sForgivingParse) == Qtrue) {
283            parser_opts = parser_opts | XML_PARSE_RECOVER;
284                        rb_iv_set( self, "@forgiving", Qtrue );
285                }
286
287        if (rb_hash_aref(opts, rb_sHtmlParse) == Qtrue) {
288            html_parser = 1;
289        }
290    }
291
292        if (rb_respond_to( xml_doc_str, s_readlines )) {
293                lines = rb_funcall( xml_doc_str, s_readlines, 0 );
294                data_s = rb_funcall( lines, s_to_s, 0 );
295        }
296        else
297        data_s = rb_obj_as_string( xml_doc_str );
298   
299        rb_iv_set( self, "@raw_data", data_s );
300
301    data = ALLOC(fxml_data_t);
302    memset( data, (int)NULL, sizeof(fxml_data_t) );
303
304    if (html_parser == 0)
305        data->doc = xmlReadMemory( RSTRING_PTR(data_s), RSTRING_LEN(data_s), 
306                               "noname.xml", NULL, parser_opts );
307    else
308        data->doc = htmlReadMemory( RSTRING_PTR(data_s), RSTRING_LEN(data_s),
309                                    "noname.html", NULL, HTML_PARSE_RECOVER | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING );
310
311    // if we're mallformed we might want to use xmlRecoverMemcory(char*, int)
312    if (data->doc == NULL) {
313        rb_raise( rb_eRuntimeError, "Failed to parse document" );
314            return Qnil;
315    }
316   
317    dv = Data_Wrap_Struct( rb_cObject, fastxml_data_mark, fastxml_data_free, data );
318    rb_iv_set(self, "@lxml_doc", dv );
319
320    if (blk != Qnil)
321        rb_yield( self );
322
323    return self;
324}
325
326
327/* }}} fastxml_doc
328 */
Note: See TracBrowser for help on using the browser.