xslt4r-0.0.2/ 40755 1750 0 0 7375044136 11601 5ustar michaelwheelxslt4r-0.0.2/ChangeLog100644 1750 0 210 7375043347 13424 0ustar michaelwheel=begin = ChangeLog : 0.0.2 (2001-11-15) * redist xmlscan with XSLT4R * modified XSLT::Stylesheet#initialize arguments : 0.0.1 =end xslt4r-0.0.2/ToDo100644 1750 0 1173 7332221514 12456 0ustar michaelwheel=begin = ToDo * * : => ... * xsl:param * xsl:text * XPath functions: ruby:func(a, b) => a.func(b) in Ruby * xxxx, same with xsl:param * build a output tree = Done * xsl:include * xsl:sort * xsl:call-template * xsl:choose * parse_node etc.. not with @content -> as parameter * extensions: ... * scope of variables * parse attributes of tags for {... } and eval this as XPath expression. How to disable this for specific elements? * =end xslt4r-0.0.2/xslt.rb100644 1750 0 45217 7375043161 13243 0ustar michaelwheel# # XSLT processor # # Copyright (c) 2001 by Michael Neumann (neumann@s-direktnet.de) # # $Id: xslt.rb,v 1.18 2001/11/15 22:49:21 michael Exp $ # # # Parameter of XPath extension functions: context, *args # Parameter of XSLT extension functions: template, context, processor, node, content # require "xpathtree" # # override XPath::Context#funcall to allow # user defined XPath functions # module XPath class Context attr_reader :variables alias __old_funcall funcall def funcall(name, *args) __old_funcall(name, *args) rescue XPath::NameError @callback.call( self, name, *args ) unless @callback.nil? end def register_callback( b ) @callback = b end end # class Context end # module XPath module XSLT XSLT_NS = "http://www.w3.org/1999/XSL/Transform" RUBY_NS = "http://www.fantasy-coders.de/xslt/ruby" XSLT_EXT_NS = "http://www.fantasy-coders.de/xslt/ext" module Utils def get_attr( node, name ) attr = node.attributes.find { | a | a.qualified_name == name } attr ? attr.string_value : nil end def to_xpath( val, context = nil ) case val when Array XPath::XPathNodeSet.new( context, *val ) when String XPath::XPathString.new val when Numeric XPath::XPathNumber.new Float(val) when TrueClass, FalseClass val ? XPath::XPathTrue : XPath::XPathFalse else val end end def to_ruby( val ) if val.kind_of? XPath::XPathObject val.to_ruby else val end end end # # encapsulates parsing and evaluating an attribute of the form attr="{any xpath expr}" # class Interpolated_Attribute include Utils def self.interpolate( attrValue, context ) self.new( attrValue ).eval( context ) end def initialize( attrValue ) @arr = attrValue.scan( /([^{]*)([{]([^}]*)[}])?/ ).collect { | str, _, xpath | [ str, xpath ? XPath.compile( "string(#{ xpath })" ) : nil ] } end def eval( context ) str = "" @arr.each { | s, xpath | str << "#{ s }#{ xpath ? to_ruby( xpath.call( context ) ) : '' }" } str end end # # encapsulates a xsl:sort tag # class XSL_Sort include Utils def initialize( node ) select = get_attr( node, "select" ) || "." @order = get_attr( node, "order") || "ascending" data_type = get_attr( node, "data-type" ) || "text" unless %w(ascending descending).include? @order raise "xsl:sort 'order' attribute must be either ascending or descending!" end @xpath = case data_type when "text" then XPath.compile( "string(#{ select })" ) when "number" then XPath.compile( "number(#{ select })" ) else raise "xsl:sort 'data-type' attribute supports either text or number!" end end def sort( node_array, context ) arr = node_array.collect { | node | [ @xpath.call( XPath::Context.new( node, context.node.namespace_decls, context.variables ) ), node ] } case @order when "ascending" then arr.sort! { | a, b | a[0] <=> b[0] } when "descending" then arr.sort! { | a, b | b[0] <=> a[0] } end arr.collect { | cmp, node | node } end end # # encapsulates a xsl:template tag # class Template include Utils attr_reader :name, :mode, :match, :position def initialize( node, position, ext_namespaces = [] ) @position = position @ext_namespaces = ext_namespaces # match match = get_attr( node, "match" ) if match != nil @match = XPath.compile( match ) else @match = nil end # name @name = get_attr( node, "name" ) # mode @mode = get_attr( node, "mode" ) @content = [] parse_children( node, @content ) end def add( stringOrObject, content ) if content.last.kind_of? String and stringOrObject.kind_of? String content.last << stringOrObject else content << stringOrObject end end def parse_children( node, content ) node.children.each { | child | parse_node( child, content ) } end def parse_node( node, content ) case node.node_type when :text add( node.string_value, content ) when :element then # check if it is a xsl node if node.namespace_uri == XSLT_NS parse_xsl_tag( node, content ) elsif @ext_namespaces.include? node.namespace_uri parse_ext_tag( node, content ) else add( "<#{node.qualified_name}", content ) node.attributes.each { | attr | iattr = Interpolated_Attribute.new( attr.string_value ) #arr = attr.string_value.scan( /([^{]*)([{]([^}]*)[}])?/ ).collect { | str, _, xpath | # [ str, xpath ? XPath.compile( "string(#{ xpath })" ) : nil ] #} aProc = proc { | context, processor | str = "" str << %{ #{ attr.qualified_name }="} str << iattr.eval( context ) #arr.each { | s, xpath | # str << "#{ s }#{ xpath ? to_ruby( xpath.call( context ) ) : '' }" #} str << '"' str } add( [ node, aProc ], content ) } add( ">", content ) parse_children( node, content ) add( "", content ) end end end def parse_ext_tag( node, content ) new_content = [] parse_children( node, new_content ) aProc = proc { | context, processor | obj = processor.ext_elements[node.namespace_uri] || raise( "extension not defined" ) obj.send( node.name_localpart.tr("-", "_"), self, context, processor, node, new_content ) } add( [node, aProc], content ) end def parse_xsl_tag( node, content ) case node.name_localpart # if ------------------------------------------------- when "if" test = get_attr( node, "test" ) || raise( "missing test attribute in xsl:if" ) xpath = XPath.compile( "boolean(#{ test })" ) new_content = [] parse_children( node, new_content ) aProc = proc { | context, processor | xpath.call( context ) ? new_content : nil } add( [node, aProc], content ) # choose ------------------------------------------------- when "choose" whens = node.children.select { | child | child.node_type == :element and child.namespace_uri == XSLT_NS and child.name_localpart == "when" }.collect { | child | test = get_attr( child, "test" ) || raise( "missing test attribute in xsl:when" ) xpath = XPath.compile( "boolean(#{ test })" ) new_content = [] parse_children( child, new_content ) # return: [xpath, new_content] } otherwise = node.children.find { | child | child.node_type == :element and child.namespace_uri == XSLT_NS and child.name_localpart == "otherwise" } if otherwise != nil new_content = [] parse_children( otherwise, new_content ) # return: otherwise = [nil, new_content] end aProc = proc { | context, processor | found = whens.find { | xpath, _ | xpath.call( context ) } || otherwise if found found[1] else nil end } add( [node, aProc], content ) # text ---------------------------------------------- when "text" add( node.string_value, content ) # value-of ------------------------------------------ when "value-of" xpath = XPath.compile( get_attr( node, "select" ) || raise( "missing select attribute in xsl:value-of" ) ) aProc = proc { | context, processor | to_xpath( xpath.call( context ), context ).to_str } add( [node, aProc], content ) # variable ------------------------------------------ when "variable" name = get_attr( node, "name" ) || raise( "missing name attribute in xsl:variable" ) select = get_attr( node, "select" ) || raise( "missing select attribute in xsl:variable" ) xpath = XPath.compile( select ) aProc = proc { | context, processor | # change variables with this because context.variables points to # variables context.variables[name] = to_xpath( xpath.call( context ), context ) } add( [node, aProc], content ) # apply-templates ----------------------------------- when "apply-templates" select = get_attr( node, "select" ) mode = get_attr( node, "mode" ) xpath = select ? XPath.compile( select ) : nil sorts = node.children.select { | child | child.node_type == :element and child.namespace_uri == XSLT_NS and child.name_localpart == "sort" }.collect { | child | XSL_Sort.new( child ) } aProc = proc { | context, processor | chlds = (xpath ? xpath.call( context ) : context.node.children) sorts.reverse_each { | sort | chlds = sort.sort( chlds, context ) } chlds.each { | n | processor.apply_template_for_node( n, context, mode ) } nil } add( [node, aProc], content ) # for-each ------------------------------------------ when "for-each" select = get_attr( node, "select" ) xpath = select ? XPath.compile( select ) : nil sorts = [] cont = [] # other nodes than xsl:sort are compiled and stored here node.children.each { | child | if child.node_type == :element and child.namespace_uri == XSLT_NS and child.name_localpart == "sort" then sorts << XSL_Sort.new( child ) else parse_node( child, cont ) end } aProc = proc { | context, processor | chlds = (xpath ? xpath.call( context ) : context.node.children) sorts.reverse_each { | sort | chlds = sort.sort( chlds, context ) } chlds.each { | n | apply( processor, n, cont, context.variables ) } nil } add( [node, aProc], content ) # call-template ----------------------------------- when "call-template" name = get_attr( node, "name" ) # substitue { .. } ??? aProc = proc { | context, processor | templ = processor.template_by_name( name ) || raise( "template '#{ name }' not found" ) templ.apply( processor, context.node, nil, processor.glob_vars ) nil } add( [node, aProc], content ) else raise "unknown xsl tag: #{node.name_localpart}" end end # runtime - methods ----------------------------------------- def match?( node, context, mode ) return false if @match.nil? or @mode != mode @match.call( context ).include? node end def apply( processor, node, content = nil, variables = {} ) (content || @content).each { | obj | case obj when String processor << obj when Array nd, procObj = obj context = XPath::Context.new( node, nd.namespace_decls, variables ) processor.install_extensions( context ) res = procObj.call( context, processor ) # process return value case res when String processor << res when Array apply( processor, node, res, variables ) end end } end # self > template: self <=> template = 1 def <=>( template ) cmp = @match.source.size <=> template.match.source.size if cmp == 0 @position < template.position ? 1 : -1 else cmp end end end # # encapsulates a whole xslt stylesheet # class Stylesheet include Utils attr_reader :templates attr_reader :ext_elements # for Template attr_accessor :output def <<( stringOrReadable ) output.each { | outp | outp << stringOrReadable } end def initialize( stringOrReadable, arguments = {} ) @pos = 0 @templates = [] @ext_functions = {XSLT_EXT_NS => [XsltExtFunctions.new, true] } @ext_elements = {RUBY_NS => XsltRubyExt.new, XSLT_EXT_NS => XsltExtElements.new} @ext_namespaces = [RUBY_NS, XSLT_EXT_NS] # # # @params = [] parse_stylesheet( stringOrReadable ) @output = [$stdout] # # program arguments => XsltExtFuntions#get_arg( key ) # @args = arguments end # Extension Classes --------------------------------------------- class XsltRubyExt def eval( template, context, processor, node, content ) $XSLT_OUTPUT = "" Kernel.eval node.string_value, TOPLEVEL_BINDING $XSLT_OUTPUT end end class XsltExtFunctions def get_arg( key ) @args[ key ] end end class XsltExtElements include Utils def apply_external_stylesheet( template, context, processor, node, content ) stylesheet = get_attr( node, "stylesheet" ) || raise( "no stylesheet attribute given" ) document = get_attr( node, "document" ) || raise( "no document attribute given" ) stylesheet = Interpolated_Attribute.interpolate( stylesheet, context ) document = Interpolated_Attribute.interpolate( document, context ) sh = File.readlines( stylesheet ).to_s dc = File.readlines( document ).to_s style = XSLT::Stylesheet.new( sh ) style.output = processor.output style.apply( dc ) nil end # node is the XSLT node # context is XML document context def redirect( template, context, processor, node, content ) file = get_attr( node, "file" ) || raise( "no file attribute given" ) file = Interpolated_Attribute.interpolate( file, context ) f = File.open(file, "w+") old_output = processor.output processor.output = [f] template.apply( processor, context.node, content, context.variables ) f.close processor.output = old_output nil end def output_file( template, context, processor, node, content ) file = get_attr( node, "file" ) || raise( "no file attribute given" ) file = Interpolated_Attribute.interpolate( file, context ) File.readlines( file ).to_s end end private # --------------------------------------------------------- def parse_stylesheet( stringOrReadable ) root = XPath::DataModel::Builder.new.parse( stringOrReadable ) stylesheet = root.children[0] @ext_namespaces |= ( get_attr( stylesheet, "extension-element-prefixes" ) || "" ).split(/\s+/).collect { | prefix | stylesheet.namespace_decls[prefix] || raise( "namespace not defined for extension element" ) } stylesheet.children.each { | node | case node.node_type when :text # ignore when :element case node.namespace_uri when XSLT_NS case node.name_localpart when 'template' @templates << Template.new( node, @pos += 1, @ext_namespaces ) when 'include' href = get_attr( node, "href" ) || raise( "missing attribute 'href' in xsl:include" ) parse_stylesheet( get_string_from_href( href ) ) when 'param' # name, select name = get_attr( node, "name" ) || raise( "missing attribute 'name' in xsl:param" ) select = get_attr( node, "select" ) || raise( "missing attribute 'select' in xsl:param" ) @params << [name, XPath.compile( select )] end when XSLT_EXT_NS case node.name_localpart when 'functions' prefix = get_attr( node, "prefix" ) || raise( "missing attribute 'prefix' in lxslt:functions" ) ns = node.namespace_decls[prefix] || raise( "missing namespace in lxslt:functions" ) object = get_attr( node, "object" ) convert = get_attr( node, "convert" ) == "true" ? true : false code = node.string_value eval code, TOPLEVEL_BINDING obj = eval object, TOPLEVEL_BINDING @ext_functions[ns] = [obj, convert] when 'elements' prefix = get_attr( node, "prefix" ) || raise( "missing attribute 'prefix' in lxslt:elements" ) ns = node.namespace_decls[prefix] || raise( "missing namespace in lxslt:elements" ) object = get_attr( node, "object" ) code = node.string_value eval code, TOPLEVEL_BINDING obj = eval object, TOPLEVEL_BINDING @ext_elements[ns] = obj end end end } end def get_string_from_href( href ) File.readlines( href ).to_s end private # Context ------------------------------------------------- def fun_callback( context, name, *args ) ns_prefix, fun_name = name.split(":") ns = context.get_namespace(ns_prefix) fun_obj, convert = @ext_functions[ns] if fun_obj != nil args = args.collect { | a | to_ruby(a) } if convert res = fun_obj.send(fun_name, context, *args) res = to_xpath(res) if convert res else case ns when RUBY_NS args = args.collect { | a | to_ruby(a) } to_xpath( eval("#{fun_name}( *args )"), context ) else raise "Function not defined" end end end public # User functions ------------------------------------------- def apply( stringOrReadable ) root = XPath::DataModel::Builder.new.parse( stringOrReadable ) context = XPath::Context.new( root, root.namespace_decls ) @glob_vars = {} install_extensions( context ) @params.each { | name, xpath | @glob_vars[ name ] = to_xpath( xpath.call( context ) ) context = XPath::Context.new( root, root.namespace_decls, @glob_vars ) install_extensions( context ) } apply_template_for_node( root, context ) end public # Template functions --------------------------------------- def glob_vars new = {} @glob_vars.each {| k, v | new[k] = if v.nil? then nil else v.dup end } new end def install_extensions( context ) context.register_callback( method(:fun_callback).to_proc ) end # mode is template:mode attribute def apply_template_for_node( node, context, mode = nil ) # find best matching template for node ... matching = @templates.select { | template | template.match?( node, context, mode ) }.max # ... and apply it matching.apply( self, node, nil, self.glob_vars ) if matching != nil end def template_by_name( name ) @templates.find { | template | template.name == name } end end # class Stylesheet end # module XSLT if $0 == __FILE__ if ARGV.size < 2 puts "USAGE: #$0 stylesheet xml-document [ key=value [ ... ] ]" puts " e.g. #$0 style.xsl data.xml" exit 1 end stylesheet = File.readlines( ARGV[0] ).to_s xml_document = File.readlines( ARGV[1] ).to_s args = {} (ARGV[2..-1] || []).each { |str| k, v = str.split("="); args[k] = v } stylesheet = XSLT::Stylesheet.new( stylesheet, args ) stylesheet.apply( xml_document ) end xslt4r-0.0.2/redist/ 40755 1750 0 0 7375044136 13073 5ustar michaelwheelxslt4r-0.0.2/redist/xmlscan-0.0.10.tar.gz100644 1750 0 644000 7375043236 16542 0ustar michaelwheelxmlscan-0.0.10/0040755000175000017500000000000007225067612012322 5ustar katsuusersxmlscan-0.0.10/htmlscan.rb0100644000175000017500000001337607225067064014470 0ustar katsuusers# # htmlscan.rb # # Copyright (C) Ueno Katsuhiro 2000,2001 # # $Id: htmlscan.rb,v 1.5 2001/01/04 12:36:04 katsu Exp $ # require 'xmlscan' class XMLScanner module HTML private def entityref_literal(ref) PredefinedEntity[ref] end def on_attribute_value(key, val) if val then inc = 0 val.gsub!(/&([^;\s]+?\b);?/) { |m| if (s = $1)[0] == ?\# then rep = parse_charref(s) else rep = entityref_literal(s) end unless rep then @unexpanded_entityrefs = [] unless defined? @unexpanded_entityrefs @unexpanded_entityrefs.push [ key.dup, $~.begin(0) + inc, s ] rep = '' end inc += rep.size - m.size rep } end true end def scan_content(s) while true unless /&/ =~ s then on_chardata s else on_chardata s unless (s = $`).empty? $'.split(/&/, -1).each { |i| unless /\A([^;\s]+?\b);?/ =~ i then i = '&' << i else e, i = $1, $' if e[0] == ?\# then parse_charref e else on_entityref e end end on_chardata i unless i.empty? } end break if @src.tag_start? s = @src.pop break unless s on_chardata '>' unless s == '>' end end def scan_pi(s) s[0,2] = '' pi = s until @src.tag_end? s = @src.pop unless s then parse_error "unterminated PI meets EOF" break end pi << '>' if s[0] != ?< pi << s end on_pi '', pi end def scan_stag(s) attr = {} unless /(?=[\/\s])/ =~ s then name = s name[0,1] = '' if name.empty? then # << or <> if @src.tag_end? then parse_error "found an empty start tag `<>'" else parse_error "parse error at `<'" return on_chardata('<' + s) end end else name, s = $`, $' name[0,1] = '' if name.empty? then # < tag parse_error "parse error at `<'" unless /\A(?:(?!\n\s*\n)\s)*([^\/\s]+)/ =~ s then return on_chardata('<' << s) end name, s = $1, $' end begin complete = true s.scan(/\s+(?:([^=\s]+)(?:\s*=\s*('[^']*'?|"[^"]*"?|[^="'\s]+))?|\z)|\s*(.[^='"\s]*)/m ) { |key,val,err| if key then if val then if val[0] == ?" or val[0] == ?' then #'" qmark = val.slice!(0,1) if val[-1] == qmark[0] then val.chop! else s = read_until(/#{qmark}/, val, 'attribute value') complete = false # always break here. end end end if on_attribute_value(key, val) then parse_error "doubled attribute `#{key}'" if attr.key? key attr[key] = val || true end elsif err then parse_error "parse error at `#{err.split(/\b|\s/,2)[0]}'" end } end until complete end unclosed_tag 'start tag' unless @src.tag_end? on_stag name, attr end DOCTYPEPattern = instance_eval { pidc = '[-\'()+,./:=?;!*#@$_% \\r\\na-zA-Z0-9]' pidc2 = pidc.delete("'") /\A([^\s\["']+)(?:\s+(?:SYSTEM|PUBLIC(?:\s+("#{pidc}*"|'#{pidc2}*'))?)\s+("[^"]*"?|'[^']*'?))\s*/i } def scan_doctype(s) unless DOCTYPEPattern =~ s then parse_error "parse error in DOCTYPE" return end root, pubid, sysid, s = $1, $2, $3, $' if pubid then pubid.chop! pubid[0,1] = '' pubid.gsub!(/\s+/, ' ') end if sysid then c = sysid.slice!(0,1) if c[0] == sysid[-1] then sysid.chop! else s = read_until(/#{c}\s*/, sysid, 'DOCTYPE') end end parse_error "parse error at `#{s.split(/\b|\s/,2)[0]}'" unless s.empty? unclosed_tag 'DOCTYPE' unless @src.tag_end? pubid, sysid = sysid, nil if pubid.nil? and sysid on_doctype root, pubid, sysid end def scan_prolog while s = @src.pop and not /\A' #[24] VersionInfo ::= S 'version' Eq ("'" VersionNum "'" | '"' VersionNum '"') #[80] EncodingDecl ::= S 'encoding' Eq ( '"' EncName '"' | "'" EncName "'" ) # #[29] markupdecl ::= elementdecl | AttlistDecl | EntityDecl | NotationDecl # | PI | Comment #[28a] DeclSep ::= PEReference | S # #[61] conditionalSect ::= includeSect | ignoreSect #[62] includeSect ::= '' #[63] ignoreSect ::= '' #[64] ignoreSectContents ::= Ignore ('' Ignore)* #[65] Ignore ::= Char* - (Char* ('') Char*) # #[45] elementdecl ::= '' #[46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | children #[51] Mixed ::= '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*' # | '(' S? '#PCDATA' S? ')' #[47] children ::= (choice | seq) ('?' | '*' | '+')? #[48] cp ::= (Name | choice | seq) ('?' | '*' | '+')? #[49] choice ::= '(' S? cp ( S? '|' S? cp )+ S? ')' #[50] seq ::= '(' S? cp ( S? ',' S? cp )* S? ')' # #[52] AttlistDecl ::= '' #[53] AttDef ::= S Name S AttType S DefaultDecl #[54] AttType ::= StringType | TokenizedType | EnumeratedType #[55] StringType ::= 'CDATA' #[56] TokenizedType ::= 'ID' | 'IDREF' | 'IDREFS' | 'ENTITY' | 'ENTITIES' # | 'NMTOKEN' | 'NMTOKENS' #[57] EnumeratedType ::= NotationType | Enumeration #[58] NotationType ::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')' #[59] Enumeration ::= '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')' #[60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED' | ( ('#FIXED' S)? AttValue ) #[10] AttValue ::= '"' ([^<&"] | Reference)* '"' # | "'" ([^<&'] | Reference)* "'" # #[70] EntityDecl ::= GEDecl | PEDecl #[71] GEDecl ::= '' #[72] PEDecl ::= '' #[73] EntityDef ::= EntityValue | (ExternalID NDataDecl?) #[74] PEDef ::= EntityValue | ExternalID #[9] EntityValue ::= '"' ([^%&"] | PEReference | Reference)* '"' # | "'" ([^%&'] | PEReference | Reference)* "'" #[75] ExternalID ::= 'SYSTEM' S SystemLiteral # | 'PUBLIC' S PubidLiteral S SystemLiteral #[11] SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'") #[12] PubidLiteral ::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'" #[76] NDataDecl ::= S 'NDATA' S Name # #[82] NotationDecl ::= '' #[83] PublicID ::= 'PUBLIC' S PubidLiteral # #[16] PI ::= '' Char*)))? '?>' #[17] PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l')) # #[15] Comment ::= '' class XMLDTDScanner options no_result_var rule extSubset: # none # | PI extSubsetDecls | extSubsetDecl extSubsetDecls extSubset: TextDecl extSubsetDecls { p val[0] } | extSubsetDecls extSubsetDecls: # none # | extSubsetDecls PI { p [ :PI, val[1] ] } | extSubsetDecls PEReference { p [ :PI, val[1] ] } | extSubsetDecls extSubsetDecl extSubsetDecl: '' | '' | ignore '[' { @src.ignore = true } ignoreSectContents ']]>' { @src.ignore = false } include: INCLUDE | _INCLUDE ignore: IGNORE | _IGNORE ignoreSectContents: # none # | '' markupDecl: elementDecl | attlistDecl | entityDecl | notationDecl | Comment { p [ :Comment, val[0] ] } elementDecl: ELEMENT _name contentspec contentspec: _EMPTY | _ANY | ' (' childrenContent ')' repmark repmark: # none # | '?' | '*' | '+' childrenContent: seq | choice cp: name repmark | '(' childrenContent ')' repmark seq: cp | seq ',' cp choice: cp '|' cp | choice '|' cp attlistDecl: ATTLIST _name attDef attDef: # none # | attDef _name attType defaultDecl attType: _CDATA | _ID | _IDREF | _IDREFS | _ENTITY | _ENTITIES | _NMTOKEN | _NMTOKENS | _NOTATION ' (' namelist ')' | ' (' namelist ')' namelist: name | namelist '|' name defaultDecl: _HREQUIRED | _HIMPLIED | _FIXED _String | _String entityDecl: ENTITY entity entity: _name entityDef { on_general_entity(val[0], val[1]) } | ' %' _name peDef { on_parameter_entity(val[1], val[2]) } entityDef: _String | externalID ndataDecl ndataDecl: # none # | _NDATA _name peDef: _String | externalID externalID: _SYSTEM _String { [ nil, val[1] ] } | _PUBLIC _String _String { [ val[1], val[2] ] } notationDecl: NOTATION _name notationID notationID: externalID | _PUBLIC _String { [ val[1], nil ] } _name: _Name | _keywords name: Name | _name | keywords keywords: INCLUDE | IGNORE | ELEMENT | ATTLIST | ENTITY | NOTATION # | HPCDATA _keywords: _INCLUDE | _IGNORE | _EMPTY | _ANY | _CDATA | _ID | _IDREF | _IDREFS | _ENTITY | _ENTITIES | _NMTOKEN | _NMTOKENS | _NOTATION | _SYSTEM | _PUBLIC | _NDATA # | _HREQUIRED | _HIMPLIED | _HFIXED | _HPCDATA end ---- header ---- # # xmldtd.rb : generated by racc # require 'xmlscan' require 'strscan' ---- inner ---- Keywords = {} KeywordsWithSpace = {} [ 'ELEMENT', 'ATTLIST', 'INCLUDE', 'IGNORE', '#PCDATA', 'ENTITY', 'NOTATION', ].each { |i| Keywords[i] = i.tr('#', 'H').intern } [ 'EMPTY', 'ANY', 'CDATA', 'ID', 'IDREF', 'IDREFS', 'ENTITIES', 'NMTOKEN', 'NMTOKENS', '#REQUIRED', '#IMPLIED', '#FIXED', 'SYSTEM', 'PUBLIC', 'NDATA', 'INCLUDE', 'IGNORE', '#PCDATA', 'ENTITY', 'NOTATION' ].each { |i| KeywordsWithSpace[i] = ('_' + i.tr('#', 'H')).intern } class DTDTokenizer # \s* => PI # \s+--.*?-- => Comment following space # --.*?-- => Comment # \s* ' '[' # \s*]]> => ']]>' # \s* ' Name following space # \w+ => Name # \s*> => '>' # \s+( => ' (' # ( => '(' # \s*| => '|' # \s*, => ',' # \s*) => ')' # \s+\* => ' *' # \* => '*' # \s+\? => ' ?' # \? => '?' # \s+\+ => ' +' # \+ => '+' # \s+".*?" => String following space # ".*?" => String # \s+'.*?' => String following space # '.*?' => String # \s*%\w+; => PEReference # \s+% => ' %' PortWrapper = XMLScanner::XMLSource::PortWrapper def initialize(src) @src = PortWrapper.new(src) @ignore = false @buf = [] nextline end def lineno @src.lineno end def path @src.path end attr_reader :ignore def ignore=(f) @ignore = (f != nil and f != false) end def pushback(str) @buf.push @scan if @scan str = ' ' + str @scan = StringScanner.new(str << ' ') self end private def nextline @scan = (@buf.shift or ((s = @src.gets) and StringScanner.new(s))) end def scan_until(re, t) ret = @scan.scan_until(re) unless ret then ret = @scan.rest while true raise ParseError, "unterminated #{t} meets EOF" unless nextline s = @scan.scan_until(re) break if s ret << @scan.rest end ret << s end ret end def skip_until(re) ret = @scan.skip_until(re) unless ret then ret = @scan.restsize while true return nil unless nextline n = @scan.skip_until(re) break if n ret += @scan.restsize end ret += n end ret end def skip_space begin n = @scan.skip(/\s+/) break unless n end while @scan.empty? and nextline n end def scan_pi unless @scan.scan(/([^\s\?\>]+)(?:\s+|(?=\?>))/) then raise ParseError, "parse error at `/, 'PI') s.chop!.chop! if name == 'xml' then [ :TextDecl, s ] else [ :PI, [ name, s ] ] end end def on_name(s, space) if space then [ (KeywordsWithSpace[s] or :_Name), s ] else [ (Keywords[s] or :Name), s ] end end def on_pe_ref(s) [ :PEReference, s ] end def on_delimiter(c) skip_space [ c, c ] end public Delim = '\\s!"%&\'\\(\\)\\*\\+,\\/<=>\\?\\[\\]\\|' def next_token nextline while @scan and @scan.empty? space = skip_space if @scan if not @scan then [ false, :eof ] elsif @ignore then if @scan.skip_until(//) then s = @scan[0] [ s, s ] else [ false, :eof ] end elsif s = @scan.scan(/[^-#{Delim}][^#{Delim}]*/o) then on_name s, space elsif s = @scan.scan(/<\?||--|[\[>()|,*?+%'"]/) then if s == '--' then s = scan_until(/--/, 'comment') s.chop!.chop! if space then [ :_Comment, s ] else [ :Comment, s ] end elsif s == '' or @eof then # at(1) == (size > 1) super else begin src = @port.gets unless src then @eof = true break end a = src.split(/(?=>?<)|>/, -1) a[0] = super << a[0] if last and (c = a[0][0]) != ?< and c != ?> concat a end until at(1) # at(1) == (size > 1) reverse! @lineno_size = size - 1 @lineno_count = super end end def tag_end? s = last and s[0] != ?< end def tag_start? s = last and s[0] == ?< end def eof? @eof and empty? end def lineno unless size == @lineno_size then @port.lineno else unless @lineno_count.is_a? Integer then if @lineno_count then @lineno_count = @lineno_count.sub(/\A\s+/, '').scan(/^/).size - 1 else @lineno_count = 0 end end @port.lineno - @lineno_count end end def path @port.path end def send_port(method, *args) if @port.is_a? PortWrapper then @port.send_port(method, *args) else @port.send(method, *args) end end end def initialize(port = nil) @src = XMLSource.new @prolog = false feed port if port end def feed(port) @src.feed port @prolog = true self end attr_reader :prolog alias in_prolog? prolog undef prolog private def on_error(path, lineno, msg) raise ParseError, sprintf('%s:%d: %s', path, lineno, msg) end def on_xmldecl(version, encoding, standalone) end def on_doctype(root, pubid, sysid) end def on_comment(strs) end def on_pi(target, pi) end def on_chardata(str) end def on_etag(name) end def on_stag(name, attr) end def on_emptyelem(name, attr) end def on_entityref(ref) s = entityref_literal(ref) on_chardata s if s end def on_charref(ref) s = parse_charref(ref) on_chardata s if s end def on_eof end private def parse_error(msg) on_error @src.path, @src.lineno, msg end PredefinedEntity = { 'lt' => '<', 'gt' => '>', 'quot' => '"', 'apos' => '\'', 'amp' => '&', } def charref_literal(code) [code].pack('N').sub(/\A\000+/mn, '') end def entityref_literal(ref) PredefinedEntity[ref] or begin parse_error "undefined general entity `#{ref}'" nil end end def parse_charref(ref) if /\A#(\d+)\z/ =~ ref then charref_literal $1.to_i elsif /\A#x([\dA-Fa-f]+)\z/ =~ ref then charref_literal $1.hex else parse_error "parse error at `&#{ref}'" nil end end def on_attribute_value(key, val) inc = 0 val.gsub!(/&([^;\s<>]+)?;?/) { |m| if m[-1] == ?; and (s = $1) then if s[0] == ?\# then rep = parse_charref(s) else rep = entityref_literal(s) end unless rep then @unexpanded_entityrefs = [] unless defined? @unexpanded_entityrefs @unexpanded_entityrefs.push [ key.dup, $~.begin(0) + inc, s ] rep = '' end inc += rep.size - m.size rep else parse_error "parse error at `#{m}'" m end } true end def scan_content(s) while true unless /&/ =~ s then on_chardata s else on_chardata s unless (s = $`).empty? $'.split(/&/, -1).each { |i| unless /;/ =~ i then parse_error "parse error at `&#{i.split(/\b|\s/,2)[0]}'" on_chardata('&' << i) next end e, i = $`, $' if /\s/ =~ e then parse_error "parse error at `&#{$`}'" on_chardata('&' << i) next end if e[0] == ?\# then parse_charref e else on_entityref e end on_chardata i unless i.empty? } end break if @src.tag_start? s = @src.pop break unless s s[0,0] = '>' unless s == '>' end end def scan_comment(s) s[0,4] = '' # '" until @src.tag_end? and (s = @src.pop) comm.push '>' if (c = s[0]) != ?< and c != ?> comm.push s end end on_comment comm end def scan_pi(s) unless /\A<\?(\S+)(?:\s+|(?=\?\z))/ =~ s then parse_error "parse error at `' if (c = s[0]) != ?< and c != ?> pi << s end pi.chop! on_pi target, pi end end def scan_cdata(s) cdata = [ s ] until @src.tag_end? and s[-1] == ?] and s[-2] == ?] s = @src.pop unless s then parse_error "unterminated CDATA section meets EOF" return on_chardata(cdata.join) end cdata.push '>' if (c = s[0]) != ?< and c != ?> cdata.push s end s.chop!.chop! on_chardata cdata.join end def unclosed_tag(t) if @src.eof? then # ' if (c = s[0]) != ?< and c != ?> v, s = s.split(re, 2) dst << v end until s s end def scan_etag(s) s[0,2] = '' # '" else parse_error "parse error at ` if @src.tag_end? then parse_error "found an empty start tag `<>'" else parse_error "parse error at `<'" return on_chardata('<' + s) end end else name = $` s = $' name[0,1] = '' if name.empty? then # < tag parse_error "parse error at `<'" return on_chardata('<' + s) end begin complete = true s.scan(/\s+(?:([^\s\/=]+)\s*=\s*('[^']*'?|"[^"]*"?)|\z)|\s*(\/\z)|\s*(.[^='"\s]*)/m ) { |key,val,e,err| if key then qmark = val.slice!(0,1) if val[-1] == qmark[0] then val.chop! else complete = false re = /#{qmark}/ begin s = @src.pop if not s then parse_error "unterminated #{t} meets EOF" complete = true break elsif (c = s[0]) == ?< then parse_error "`<' is found in attribute value" elsif c != ?> then val << '>' end v, s = s.split(re, 2) val << v end until s # always break here. end if on_attribute_value(key, val) then parse_error "doubled attribute `#{key}'" if attr.key? key attr[key] = val end elsif e then method = :on_emptyelem elsif err then parse_error "parse error at `#{err.split(/\b|\s/,2)[0]}'" end } end until complete end unclosed_tag 'start tag' unless @src.tag_end? send method, name, attr end def parse_internal_dtd(s) parse_error "internal DTD subset is not supported" end DOCTYPEPattern = /\A([^\s\["']+)(?:\s+(?:SYSTEM|PUBLIC\s+("[^"]*"|'[^"']*'))\s+("[^"]*"?|'[^']*'?))\s*/ def scan_doctype(s) unless DOCTYPEPattern =~ s then parse_error "parse error in DOCTYPE" return end root, pubid, sysid, s = $1, $2, $3, $' if pubid then pubid.chop! pubid[0,1] = '' end if sysid then c = sysid.slice!(0,1) if c[0] == sysid[-1] then sysid.chop! else s = read_until(/#{c}\s*/, sysid, 'DOCTYPE') end end if s[0] == ?[ then s[0,1] = '' parse_internal_dtd s elsif not s.empty? then parse_error "parse error at `#{s.split(/\b|\s/,2)[0]}'" end unclosed_tag 'DOCTYPE' unless @src.tag_end? on_doctype root, pubid, sysid end def scan_bang_tag(s) parse_error "parse error at ` then scan_text @src.pop else scan_content s end end XMLDeclPattern, TextDeclPattern = instance_eval { version = '\\s+version\\s*=\\s*("[^"\']+"|\'[^"\']+\')' encoding = '\\s+encoding\\s*=\\s*("[^"\']+"|\'[^"\']+\')' standalone = '\\s+standalone\\s*=\\s*("[^"]+"|\'[^\']+\')' [ /\A<\?xml#{version}(?:#{encoding}(?:#{standalone})?)?\s*\?\z/, /\A<\?xml(?:#{version})?#{encoding}\s*\?\z/ ] } def scan_prolog s = @src.pop if s and /\A<\?xml\b/ =~ s then unless XMLDeclPattern =~ s then parse_error 'parse error in XML declaration' else version, encoding, standalone = $1, $2, $3 version.chop! version[0,1] = '' if encoding then encoding.chop! encoding[0,1] = '' encoding.downcase! end if standalone then standalone.chop! standalone[0,1] = '' if standalone == 'yes' then standalone = true elsif standalone == 'no' then standalone = false else parse_error 'invalid standalone document declaration' standalone = nil end end on_xmldecl version, encoding, standalone unclosed_tag 'XML declaration' unless @src.tag_end? s = @src.pop end end while s if s[0] == ?< then if (c = s[1]) == ?! then if s[2] == ?- and s[3] == ?- then scan_comment s elsif /\A 'http://www.w3.org/XML/1998/namespace', } class ElementStack < superclass::ElementStack def initialize super @namespace = {} end attr_reader :namespace def default_namespace @namespace[:default] end def get_namespace(name) @namespace[name] or PredefinedNamespace[name] end def set_namespace(name, uri) push [ name, @namespace[name] ] if uri.empty? then @namespace.delete name else @namespace[name] = uri end end def pop_element(name) if name == last then pop while log = last and not log[2] pop @namespace[log[0]] = log[1] end push nil unless log self else nil end end def each reverse_each { |i| yield i if i[2] } end end private def expand_qualified_name(name, default = nil) unless /:/ =~ name then [ default, nil, name ] else prefix, localpart = $`, $' if localpart.empty? then parse_error "parse error at `:'" return [ nil, nil, name ] elsif /:/ =~ localpart then parse_error "localpart `#{localpart}' includes a colon" end unless namespace = @elemstack.get_namespace(prefix) then parse_error "undeclared namespace `#{prefix}'" namespace = nil end [ namespace, prefix, localpart ] end end def expand_attr_namespace(attr) dst = {} attr.each { |key,val| namespace, prefix, name = expand_qualified_name(key) h = dst[namespace] dst[namespace] = h = {} unless h h[name] = val } dst end def expand_attr_namespace_2(attr) attr.keys.each { |key| namespace, prefix, name = expand_qualified_name(key) if namespace then k = namespace + ' ' + name else k = ' ' + name end attr[k] = attr.delete(key) } attr end def name_in_errmsg(name) if name[1] then "#{name[1]}:#{name[2]}" else name[2] end end def on_stag(name, attr) super(expand_qualified_name(name, @elemstack.default_namespace), expand_attr_namespace(attr)) end def on_emptyelem(name, attr) super(expand_qualified_name(name, @elemstack.default_namespace), expand_attr_namespace(attr)) end def on_etag(name) super expand_qualified_name(name, @elemstack.default_namespace) end def on_pi(target, pi) parse_error "PI target must not include `:'" if /:/ =~ target end def on_attribute_value(key, val) super f = nil if key == 'xmlns' or f = (key[0,6] == 'xmlns:') then if f then name = key[6..-1] if name.empty? then parse_error "parse error at `:'" elsif /:/ =~ name then parse_error "namespace name `#{name}' includes a colon" elsif name[0,3].downcase == 'xml' then parse_error "prefix `#{name}' is reserved" elsif /\s/ =~ val then parse_error "invalid namespace `#{val}'" elsif val.empty? then parse_error "null namespace is declared as `#{name}'" else @elemstack.set_namespace name, val end else @elemstack.set_namespace :default, val end false else true end end end =begin supported by XMLScanner Well-Formedness Constraint: °À­»ØÄê¤Î°ì°ÕÀ­ ³«»Ï¥¿¥°Ëô¤Ï¶õÍ×ÁÇ¥¿¥°¤Ç¤Ï¡¤Æ±°ì¤Î°À­Ì¾¤¬Æó²ó°Ê¾å½Ð¸½¤·¤Æ¤Ï¤Ê¤é¤Ê¤¤¡£ Well-Formedness Constraint: °À­ÃͤË<¤ò´Þ¤Þ¤Ê¤¤¤³¤È °À­ÃÍÆâ¤ÇľÀÜŪËô¤Ï´ÖÀÜŪ¤Ë»²¾È¤¹¤ë¼ÂÂΤÎÃÖ´¹¥Æ¥­¥¹¥È¤Ë¤Ï¡¤<¤ò ´Þ¤ó¤Ç¤Ï¤Ê¤é¤Ê¤¤¡£ supported by WellFormedXMLScanner Well-Formedness Constraint: Í×ÁÇ·¿¤Î¥Þ¥Ã¥Á Í×ÁǤνªÎ»¥¿¥°¤Î̾Á°¤Ï¡¤¤½¤ÎÍ×ÁǤγ«»Ï¥¿¥°¤Ë¤ª¤±¤ëÍ×ÁÇ·¿¡Ê¤Î̾Á°¡Ë¤È¥Þ¥Ã¥Á¤·¤Ê ¤±¤ì¤Ð¤Ê¤é¤Ê¤¤¡£ Well-Formedness Constraint: ¼ÂÂΤ¬Àë¸À¤µ¤ì¤Æ¤¤¤ë¤³¤È DTD¤ò¤â¤¿¤Ê¤¤Ê¸½ñ¡¤¥Ñ¥é¥á¥¿¼ÂÂλ²¾È¤ò´Þ¤Þ¤Ê¤¤ÆâÉôDTD¥µ¥Ö¥»¥Ã¥È¤À¤±¤ò¤â¤Äʸ½ñ¡¤ Ëô¤Ï "standalone='yes'" ¤ò¤â¤Äʸ½ñ¤Ë¤ª¤¤¤Æ¡¤¼ÂÂλ²¾È¤ÇÍѤ¤¤ë Name ¤Ï¡¤ ³°Éô¥µ¥Ö¥»¥Ã¥ÈµÚ¤Ó¥Ñ¥é¥á¥¿¼ÂÂÎÆâ°Ê³°¤Ë¸½¤ì¤ë¼ÂÂÎÀë¸À¤Ë´Þ¤Þ¤ì¤ë̾Á°¤È ¥Þ¥Ã¥Á¤·¤Ê¤±¤ì¤Ð¤Ê¤é¤Ê¤¤¡£¤¿¤À¤·¡¤À°·Á¼°¤Îʸ½ñ¤Ï¡¤¼ÂÂÎamp, lt, gt, apos, quot ¤òÀë¸À¤¹¤ëɬÍפϤʤ¤¡£°ìÈ̼ÂÂΤξì¹ç¤Ï¡¤Â°À­¥ê¥¹¥ÈÀë¸À¤Î ¥Ç¥Õ¥©¥ë¥ÈÃÍÆâ¤Ç¤Î»²¾È¤è¤êÀè¤Ë¡¤Àë¸À¤¬¸½¤ì¤Ê¤±¤ì¤Ð¤Ê¤é¤Ê¤¤¡£³°Éô¥µ¥Ö¥»¥Ã¥ÈËô¤Ï ³°Éô¥Ñ¥é¥á¥¿¼ÂÂΤǼÂÂΤòÀë¸À¤¹¤ë¤È¤­¡¤ÂÅÅöÀ­¤ò¸¡¾Ú¤·¤Ê¤¤¥×¥í¥»¥µ¤¬¡¤Àë¸À¤ò ÆÉ¤ß¡¤½èÍý¤¹¤ë¤³¤È¤òµÁ̳¤Å¤±¤Ê¤¤¤³¤È¤ËÃí°Õ¡£¤½¤ì¤é¤Îʸ½ñ¤Ç¤Ï¡¤¼ÂÂÎ¤Ï Àë¸À¤µ¤ì¤Ê¤±¤ì¤Ð¤Ê¤é¤Ê¤¤¤È¤¤¤¦µ¬Â§¤Ï¡¤standalone='yes'¤Î¾ì¹ç¤Î¤ß¡¤ À°·Á¼°À©Ìó¤È¤Ê¤ë¡£ supported by XMLScanner with xmldtd.rb Well-Formedness Constraint: ÆâÉô¥µ¥Ö¥»¥Ã¥ÈÆâ¤Î¥Ñ¥é¥á¥¿¼ÂÂÎ DTD¤ÎÆâÉô¥µ¥Ö¥»¥Ã¥È¤Ç¤Ï¡¤¥Ñ¥é¥á¥¿¼ÂÂλ²¾È¤Ï¡¤¥Þ¡¼¥¯ÉÕ¤±Àë¸À¤¬½Ð¸½²Äǽ¤Ê¾ì½ê¤À¤± ¤Ë½Ð¸½¤Ç¤­¤ë¡£¥Þ¡¼¥¯ÉÕ¤±Àë¸À¤Î°ìÉô¤È¤·¤Æ¤Ï½Ð¸½¤Ç¤­¤Ê¤¤¡£¤³¤ÎÀ©Ìó¤Ï¡¤³°Éô¥Ñ¥é¥á ¥¿¼ÂÂÎËô¤Ï³°Éô¥µ¥Ö¥»¥Ã¥È¤Ç¤Î»²¾È¤Ë¤ÏŬÍѤ·¤Ê¤¤¡£ Well-Formedness Constraint: DTD¤ÎÃæ ¥Ñ¥é¥á¥¿¼ÂÂλ²¾È¤Ï¡¤DTDÆâ¤Ë¤À¤±¡¤½Ð¸½¤·¤Æ¤è¤¤¡£ supported by XMLParsedEntity Well-Formedness Constraint: ºÆµ¢¤Ê¤· ²òÀÏÂоݼÂÂΤϡ¤¤½¤ì¼«ÂΤؤλ²¾È¤ò¡¤Ä¾Àܤˤâ´ÖÀܤˤâ´Þ¤ó¤Ç¤Ï¤Ê¤é¤Ê¤¤¡£ Well-Formedness Constraint: ³°Éô¼ÂÂΤؤλ²¾È¤¬¤Ê¤¤¤³¤È °À­Ãͤˤϡ¤³°Éô¼ÂÂΤؤÎľÀÜŪËô¤Ï´ÖÀÜŪ¤Ê»²¾È¤ò´Þ¤à¤³¤È¤Ï¤Ç¤­¤Ê¤¤¡£ Well-Formedness Constraint: »ÈÍѤǤ­¤ëʸ»ú ʸ»ú»²¾È¤Ç»²¾È¤¹¤ëʸ»ú¤Ï¡¤Char¤ÎÀ¸À®µ¬Â§¤Ë¥Þ¥Ã¥Á¤·¤Ê¤±¤ì¤Ð¤Ê¤é¤Ê¤¤¡£ Well-Formedness Constraint: ²òÀÏÂоݼÂÂÎ ¼ÂÂλ²¾È¤Ï¡¤²òÀÏÂоݳ°¼ÂÂΤÎ̾Á°¤ò´Þ¤ó¤Ç¤¤¤Æ¤Ï¤Ê¤é¤Ê¤¤¡£²òÀÏÂоݳ°¼ÂÂΤϡ¤ ENTITY·¿Ëô¤ÏENTITIES ·¿¤È¤·¤ÆÀë¸À¤·¤¿Â°À­ÃͤȤ·¤Æ¤À¤±»²¾È¤Ç¤­¤ë¡£ =end ## for internal general parsed entities class XMLParsedEntity < WellFormedXMLScanner def initialize(name, src) @name = name super src end #undef feed private end ## for external general parsed entities class ExtXMLScanner < XMLScanner def scan_prolog s = @src.pop if s and /\A<\?xml\b/ =~ s then unless TextDeclPattern =~ s then parse_error 'parse error in text declaration' else version, encoding = $1, $2 if encoding then encoding.chop! encoding[0,1] = '' encoding.downcase! end ret = on_xmldecl(version, encoding, nil) unclosed_tag 'text declaration' unless @src.tag_end? @prolog = false return ret end end @prolog = false s and scan_text(s) end end if __FILE__ == $0 then #class TestScanner < XMLScanner #class TestScanner < WellFormedXMLScanner class TestScanner < XMLScannerWithNamespace def on_error(path, lineno, msg) STDERR.printf "%s:%d: %s\n", path, lineno, msg end end STDOUT.sync = STDERR.sync = true if /\A--?\z/ === ARGV[0] then if ARGV.shift == '--' and ARGV.size == 1 then p = IO.popen("diff -u #{ARGV[0]} -", 'w') STDOUT.reopen p class Hash def []=(k,v) (@a ||= []).push [ k, v ] end def each(&b) @a.each(&b) if defined? @a end end end class TestScanner $".push 'xmlscan.rb' require 'xmltoken' def self.def_handler(*name) name.each { |i| eval %{ def on_#{i.downcase}(*a) super print Tokenizer::#{i}.new(*a).to_s end } } end def_handler 'CharData', 'Comment', 'PI', 'XMLDecl', 'Doctype' def_handler 'ETag', 'STag', 'EmptyElem' end end src = ARGF.read scan = TestScanner.new t1 = Time.times.utime scan.parse(src) t2 = Time.times.utime STDERR.printf "%2.3f sec\n", t2 - t1 end xmlscan-0.0.10/xmltoken.rb0100644000175000017500000001432407217644055014514 0ustar katsuusers# # xmltoken.rb # # Copyright (C) Ueno Katsuhiro 2000 # # $Id: xmltoken.rb,v 1.2 2000/12/19 11:36:13 katsu Exp $ # require 'xmlscan' class XMLScanner module Tokenizer class Node def escape!(str) str.gsub!(/&/, '&') str.gsub!(//, '>') str.gsub!(/"/, '"') str end private :escape! def inspect "#{super.split(' ',2)[0]} #{to_s.inspect}" end def content nil end def to_s content end end class CharData < Node def initialize(str) @content = str end def concat(s) @content << s end attr_reader :content def to_s escape! @content end end class Comment < Node def initialize(src) @src = src end def content @src = @src.join if @src.is_a? Array @src end def to_s "" end end class PI < Node def initialize(target, pi) @target, @content = target, pi end attr_reader :target, :content def to_s "" end end class XMLDecl < Node def initialize(version, encoding, standalone) @version, @encoding, @standalone = version, encoding, standalone end attr_reader :version, :encoding, :standalone def to_s s = %'' end end class Doctype < Node def initialize(root, pubid, sysid) @root, @pubid, @sysid = root, pubid, sysid end attr_reader :root, :pubid, :sysid def public? not pubid.nil? end def system? pubid.nil? and not sysid.nil? end def to_s s = "' end end class Tag < Node attr_reader :name end class ETag < Tag def initialize(name) @name = name end def to_s "" end end class STag < Tag def initialize(name, attr) @name, @attr = name, attr end def attr_to_s @attr.collect{ |k,v| "#{k}=\"#{escape!(v)}\"" }.unshift('').join(' ') end private :attr_to_s def to_s "<#{@name}#{attr_to_s}>" end end class EmptyElem < STag def to_s "<#{@name}#{attr_to_s}/>" end end class Reference < Node attr_reader :content end class EntityRef < Reference def initialize(name,s) @name, @content = name, s end def to_s "&#{@name};" end end class CharRef < Reference def initialize(code) @content = code end def to_s "&\##{@content};" end end def initialize(*args) super @__token_parsed__ = [] end private def entityref_literal(ref) PredefinedEntity[ref] or '' end def scan_prolog ret = super if @__token_parsed__.empty? then ret else @__token_parsed__.push ret if ret @__token_parsed__.shift end end def on_chardata(str) super if (l = @__token_parsed__[-1]) and l.is_a? CharData then l.concat str else @__token_parsed__.push CharData.new(str) end nil end def on_entityref(ref) super @__token_parsed__.push EntityRef.new(ref, entityref_literal(ref)) nil end def on_charref(ref) super @__token_parsed__.push CharRef.new(ref) nil end def on_comment(strs) super ret = Comment.new(strs) if @prolog then @__token_parsed__.push ret ret = nil end ret end def on_pi(target, pi) super ret = PI.new(target, pi) if @prolog then @__token_parsed__.push ret ret = nil end ret end def on_xmldecl(version, encoding, standalone) super @__token_parsed__.push XMLDecl.new(version, encoding, standalone) nil end def on_doctype(root, pubid, sysid) super @__token_parsed__.push Doctype.new(root, pubid, sysid) nil end def on_etag(name) super ETag.new(name) end def on_stag(name, attr) super STag.new(name, attr) end def on_emptyelem(name, attr) super EmptyElem.new(name, attr) end public def get_token unless @__token_parsed__.empty? then @__token_parsed__.shift else step or @__token_parsed__.shift end end include Enumerable def each(src = nil) @src.feed src if src yield(scan_prolog) if @prolog while true yield @__token_parsed__.shift until @__token_parsed__.empty? break unless s = @src.pop s = scan_text(s) yield s if s end self end end end class XMLTokenizer < XMLScanner include Tokenizer private :step, :parse end if $0 == __FILE__ then class TestScanner < XMLTokenizer def on_error(path, lineno, msg) STDERR.printf "%s:%d: %s\n", path, lineno, msg end end if /\A--?\z/ === ARGV[0] then if (opt = ARGV.shift) == '--' and ARGV.size == 1 then p = IO.popen("diff -u #{ARGV[0]} -", 'w') STDOUT.reopen p class Hash def []=(k,v) (@a ||= []).push [ k, v ] end def each(&b) @a.each(&b) if defined? @a end end end end src = ARGF.read scan = TestScanner.new(src) if opt then t1 = Time.times.utime scan.each { |i| print i.to_s } t2 = Time.times.utime else t1 = Time.times.utime while t = scan.get_token end t2 = Time.times.utime end STDERR.printf "%2.3f sec\n", t2 - t1 end xmlscan-0.0.10/xpath-dom.rb0100644000175000017500000001626507222662121014550 0ustar katsuusers# # xpath-dom.rb # # Copyright (C) Ueno Katsuhiro 2000 # # $Id: xpath-dom.rb,v 1.7 2000/12/28 16:03:29 katsu Exp $ # require 'xmltreebuilder' require 'xpath' module XPath module DOM class AbstractNodeAdapter < NullNodeAdapter def wrap(node, visitor) @node = node self end attr_reader :node def root @node.ownerDocument end def parent @node.parentNode end def children @node.childNodes.to_a end def each_following_siblings node = @node yield node while node = node.nextSibling end def each_preceding_siblings node = @node yield node while node = node.previousSibling end def index @node.parentNode.childNodes.to_a.index(@node) end def lang node = @node lang = nil until a = node.attributes and lang = a.getNamedItem('xml:lang') node = node.parentNode end lang and lang.nodeValue end end class TextNodeAdapter < AbstractNodeAdapter def node_type :text end def string_value @node.nodeValue end end class CommentNodeAdapter < TextNodeAdapter def node_type :comment end end class PINodeAdapter < AbstractNodeAdapter def node_type :processing_instruction end def name_localpart @node.nodeName end def string_value @node.nodeValue end end class ParentNodeAdapter < AbstractNodeAdapter def string_value dst = '' stack = @node.childNodes.to_a.reverse while node = stack.pop s = node.nodeValue dst << s if s stack.concat node.childNodes.to_a.reverse end dst end end class RootNodeAdapter < ParentNodeAdapter def node_type :root end alias root node def index 0 end end class ElementNodeAdapter < ParentNodeAdapter def wrap(node, visitor) @node = node @visitor = visitor self end def node_type :element end def name_localpart @node.nodeName end def attributes map = @node.attributes attrs = @visitor.get_attributes(@node) unless attrs then attrs = [] map.length.times { |i| attrs.push map.item(i) } @visitor.regist_attributes @node, attrs end attrs end end class AttrNodeAdapter < AbstractNodeAdapter def wrap(node, visitor) @node = node @visitor = visitor self end def node_type :attribute end def name_localpart @node.nodeName end def parent @visitor.get_attr_parent @node end def index -@visitor.get_attributes(parent).index(@node) end def string_value @node.nodeValue end end class NodeVisitor def initialize @adapters = Array.new(12, NullNodeAdapter.new) @adapters[XML::DOM::Node::ELEMENT_NODE] = ElementNodeAdapter.new @adapters[XML::DOM::Node::ATTRIBUTE_NODE] = AttrNodeAdapter.new @adapters[XML::DOM::Node::TEXT_NODE] = @adapters[XML::DOM::Node::CDATA_SECTION_NODE] = TextNodeAdapter.new @adapters[XML::DOM::Node::PROCESSING_INSTRUCTION_NODE] = PINodeAdapter.new @adapters[XML::DOM::Node::COMMENT_NODE] = CommentNodeAdapter.new @adapters[XML::DOM::Node::DOCUMENT_NODE] = RootNodeAdapter.new @attr = {} end def visit(node) @adapters[node.nodeType].wrap(node, self) end def regist_attributes(node, attrs) @attr[node] = attrs attrs.each { |i| @attr[i] = node } end def get_attributes(node) @attr[node] end def get_attr_parent(node) @attr[node] end end class Context < XPath::Context def initialize(node, namespace = nil, variable = nil) super node, namespace, variable, NodeVisitor.new end end end end module XML module DOM class Node def getNodesByXPath(xpath) xpath = XPath.compile(xpath) unless xpath.is_a? XPath::XPath ret = xpath.call(XPath::DOM::Context.new(self)) raise "return value is not NodeSet" unless ret.is_a? Array ret end def _getMyLocationInXPath(parent) n = parent.childNodes.index(self) "node()[#{n + 1}]" end def makeXPath dst = [] node = self while parent = node.parentNode dst.push node._getMyLocationInXPath(parent) node = parent end dst.reverse! '/' + dst.join('/') end end class Element def _getMyLocationInXPath(parent) name = nodeName n = parent.childNodes.to_a.select { |i| i.nodeType == ELEMENT_NODE and i.nodeName == name }.index(self) "#{name}[#{n + 1}]" end end class Text def _getMyLocationInXPath(parent) n = parent.childNodes.to_a.select { |i| i.nodeType == TEXT_NODE or i.nodeType == CDATA_SECTION_NODE }.index(self) "text()[#{n + 1}]" end end class CDATASection def _getMyLocationInXPath(parent) n = parent.childNodes.to_a.select { |i| i.nodeType == TEXT_NODE or i.nodeType == CDATA_SECTION_NODE }.index(self) "text()[#{n + 1}]" end end class Comment def _getMyLocationInXPath(parent) n = parent.childNodes.to_a.select { |i| i.nodeType == COMMENT_NODE }.index(self) "comment()[#{n + 1}]" end end class ProcessingInstruction def _getMyLocationInXPath(parent) n = parent.childNodes.to_a.select { |i| i.nodeType == PROCESSING_INSTRUCTION_NODE }.index(self) "processing-instruction()[#{n + 1}]" end end class Attr def makeXPath '@' + nodeName end end end end if $0 == __FILE__ then require 'readline' require 'uconv' $KCODE = 'U' STDOUT.sync = STDERR.sync = true module XPath @compiler = Compiler.new(true) if $DEBUG end raise "requires 1 argument" unless ARGV[0] STDERR.print "parsing #{ARGV[0]} ... " doc = XML::DOM::Builder.new.parse(File.open(ARGV[0]).read) STDERR.print "done.\n" context = XPath::DOM::Context.new(doc) while src = Readline.readline("xpath:#{context.node.makeXPath}> ", true) src = Uconv.euctou8(src) cd = (/\Acd\s+/ =~ src) src = $' if cd begin t1 = Time.times.utime proc = XPath.compile(src) t2 = Time.times.utime result = proc.call(context) t3 = Time.times.utime if result.is_a? Array and not result.empty? then result.each { |i| print i.makeXPath, "\n" } context.reuse result[0] if cd else p result end printf "compile: %.2f sec eval: %.2f sec\n", t2 - t1, t3 - t2 rescue XPath::Error at = $@ printf "%s: %s (%s)\n", at.shift, $!, $!.type at.each { |i| print "\tfrom ", i, "\n" } end end end xmlscan-0.0.10/xpath.ry0100644000175000017500000014424407222662100014016 0ustar katsuusers# # xpath.ry # # Copyright (C) Ueno Katsuhiro 2000 # # $Id: xpath.ry,v 1.32 2000/12/28 16:03:12 katsu Exp $ # class Compiler prechigh left '|' right NEG left MUL 'div' 'mod' left '+' '-' left '<' '>' '<=' '>=' left '=' '!=' left 'and' left 'or' preclow options no_result_var rule xPath: # none # { [] } | expr { expr = val[0].expr('.to_ruby') expr.collect! { |i| i or @context } expr } | PATTERN pattern # for XSLT { expr = val[0].expr('.to_ruby') expr.collect! { |i| i or @context } expr } pattern: locationPath | pattern '|' locationPath { val[0] ** val[2] } expr: expr 'or' expr { val[0].logical_or val[2] } | expr 'and' expr { val[0].logical_and val[2] } | expr '=' expr { val[0].eq val[2] } | expr '!=' expr { val[0].neq val[2] } | expr '<' expr { val[0].lt val[2] } | expr '>' expr { val[0].gt val[2] } | expr '<=' expr { val[0].le val[2] } | expr '>=' expr { val[0].ge val[2] } | expr '+' expr { val[0] + val[2] } | expr '-' expr { val[0] - val[2] } | '-' expr =NEG { -val[1] } | expr MUL expr { val[0] * val[2] } | expr 'div' expr { val[0] / val[2] } | expr 'mod' expr { val[0] % val[2] } | expr '|' expr { # Why `**' is used for unionizing node-sets is that its # precedence is higher than any other binary operators # in Ruby. val[0] ** val[2] } | locationPath | filterExpr | filterExpr '/' relPath { val[0] << val[2] } | filterExpr '//' relPath { val[0].add_step('descendant-or-self') << val[2] } filterExpr: Variable { Expression.new [ nil,'.get_variable(',val[0].dump,')' ] } | '(' expr ')' { val[1].unarize } | Literal { Expression.new StringConstant.new(val[0]) } | Number { Expression.new NumberConstant.new(val[0]) } | functionCall { Expression.new val[0] } | filterExpr predicate { val[0].add_predicate val[1] } functionCall: FuncName '(' arguments ')' { val[2][0,0] = [ nil, ".funcall(#{val[0].dump}" ] val[2].push(')') } arguments: # none # { [] } | expr { val[0].expr.unshift ', ' } | arguments ',' expr { val[0].push(', ').concat(val[2].expr) } predicate: '[' { c = @context @context = c.succ c } expr { c = @context @context = _values[-2] c } ']' { expr = val[2] valuetype = expr.value_type value = expr.value if valuetype == :number then if value then f = value.to_f if f > 0 and f.truncate == f then [ ".at(#{f.to_i})" ] else [ '.at(0)' ] # clear end else expr.expr('.to_f'). unshift('.at(').push(')') end elsif value then if value.true? then [] else [ '.at(0)' ] # clear end else c = val[3] if valuetype == :ruby_boolean then conv = '.true?' else conv = '.to_predicate' end a = expr.expr(conv) a.collect! { |i| i or c } a.unshift(".predicate { |#{c}| ").push(' }') end } locationPath: '/' { LocationPath.new.absolute! } | '/' relPath { val[1].absolute! } | '//' relPath { path = LocationPath.new path.absolute! path.add_step('descendant-or-self') << val[1] } | relPath relPath: step { LocationPath.new.add_step(*val[0]) } | relPath '/' step { val[0].add_step(*val[2]) } | relPath '//' step { val[0].add_step('descendant-or-self').add_step(*val[2]) } # XPath does not permit functions here, but XPointer does. | relPath '/' FuncName '(' { c = @context @context = c.succ c } arguments { c = @context @context = _values[-2] c } ')' { on_error unless is_xpointer? args = val[5] c = val[6] args.collect! { |i| i or c } args[0] = ".funcall(#{val[2].dump}) { |#{c}| [" args.push '] }' val[0].add_predicate args } step: '.' { [ 'self', false, false, false, nil ] } | '..' { [ 'parent', false, false, false, nil ] } | axisSpec nodeTest predicates { nodetest = val[1] unless nodetest[0] then axis = val[0] if axis != 'attribute' and axis != 'namespace' then nodetest[0] = 'element' end end nodetest[0] = false if nodetest[0] == 'node' nodetest.unshift(val[0]).push(val[2]) } predicates: # none # | predicates predicate { (val[0] || []).concat val[1] } nodeTest: '*' { [ false, false, false ] } | Name { if /:/ =~ val[0] then [ false, $', $` ] #' <= for racc else [ false, val[0], nil ] end } | Name ':' '*' { on_error if /:/ =~ val[0] [ false, false, val[0] ] } | NodeType '(' nodeTestArg ')' { nodetype = val[0] arg = val[2] if arg and nodetype != 'processing-instruction' then raise CompileError, "nodetest #{nodetype}() requires no argument" end [ nodetype, arg || false, false ] } nodeTestArg: # none # | Literal axisSpec: # none # { 'child' } | '@' { 'attribute' } | AxisName '::' end ---- inner ---- module CompilePhaseObject def invoke_conv(expr, conv_method) return unless conv_method if conv_method == '.to_number' or conv_method == '.to_string' or conv_method == '.to_boolean' then expr.push conv_method, '(', nil, ')' else expr.push conv_method end end private :invoke_conv end module ConstantObject include CompilePhaseObject def to_string StringConstant.new to_str end def to_number NumberConstant.new self end def to_boolean if true? then ConstantTrue else ConstantFalse end end end module BooleanConstant include ConstantObject def value_type :boolean end def expr(conv_method = nil) if conv_method == '.to_ruby' or conv_method == '.true?' then [ true?.to_s ] else ret = [ nil, '.make_boolean(', true?.to_s, ')' ] invoke_conv ret, conv_method unless conv_method == '.to_boolean' ret end end end class ConstantTrueClass < XPathTrueClass include BooleanConstant @instance = new end class ConstantFalseClass < XPathFalseClass include BooleanConstant @instance = new end ConstantTrue = ConstantTrueClass.instance ConstantFalse = ConstantFalseClass.instance class NumberConstant < XPathNumber include ConstantObject def value_type :number end def initialize(src) f = src.to_f if src.is_a? ConstantObject and s = dump_float(f) then src = s end @src = [ src ] @precedence = 1 super f end attr_reader :precedence protected :precedence def to_number self end def expr(conv_method = nil) @src.collect! { |i| if i.is_a? ConstantObject then i.expr '.to_f' else i end } expr = @src expr.flatten! @src = :draff # for debug unless conv_method == '.to_ruby' or conv_method == '.to_f' then expr[0, 0] = [ nil, '.make_number(' ] expr.push(')') invoke_conv expr, conv_method unless conv_method == '.to_number' end expr end private def dump_float(f) if f.finite? and f == eval(s = f.to_s) then s elsif f.infinite? then if f > 0 then '(1.0 / 0.0)' else '(-1.0 / 0.0)' end elsif f.nan? then '(0.0 / 0.0)' else nil end end def concat(op, other, prec) @src.unshift('(').push(')') if @precedence < prec if other.precedence < prec then @src.push(op).push('(').concat(other.expr('.to_f')).push(')') else @src.push(op).concat(other.expr('.to_f')) end @precedence = prec end public def self.def_arithmetic_operator(op, precedence) module_eval <<_, __FILE__, __LINE__ + 1 def #{op}(other) super other if s = dump_float(@value) then @src.clear @src.push s else concat ' #{op} ', other, #{precedence} end self end _ end def_arithmetic_operator '+', 0 def_arithmetic_operator '-', 0 def_arithmetic_operator '*', 1 def_arithmetic_operator '/', 1 class << self undef def_arithmetic_operator end def %(other) orig = @value super other if s = dump_float(@value) then @src.clear @src.push s else f = other.to_f other = -other if orig % f == -@value concat ' % ', other, 1 end self end def -@ super if s = dump_float(@value) then @src.clear @src.push s else if @src.size == 1 then @src.unshift '-' else @src.unshift('-(').push(')') end @precedence = 1 end self end end class StringConstant < XPathString include ConstantObject def value_type :string end def to_string self end def expr(conv_method = nil) if conv_method == '.to_ruby' or conv_method == '.to_str' then [ @value.dump ] else ret = [ nil, '.make_string(', @value.dump, ')' ] invoke_conv ret, conv_method unless conv_method == '.to_string' ret end end end class Expression include CompilePhaseObject def initialize(expr) if expr.is_a? ConstantObject then @value = expr else raise "BUG" unless expr.is_a? Array @value = nil @valuetype = nil @expr = expr end @unary = true end attr_reader :value def value_type if @value then @value.value_type else @valuetype end end def unarize unless @unary then @expr.unshift('(').push(')') @unary = true end self end def self.def_comparison_operator(name, op) module_eval <<_, __FILE__, __LINE__ + 1 def #{name}(other) if @value and other.value then if @value #{op} other.value then @value = ConstantTrue else @value = ConstantFalse end @unary = true else @expr = expr.push(' #{op} ').concat(other.expr) @valuetype = :ruby_boolean @unary = false end self end _ end def self.def_arithmetic_operator(*ops) ops.each { |op| module_eval <<_, __FILE__, __LINE__ + 1 def #{op}(other) if @value and other.value then @value = @value.to_number #{op} other.value.to_number else @expr = expr('.to_number').push(' #{op} ') # not 'to_number', for a little speed up :-) @expr.concat other.expr('.to_f') @valuetype = :number @unary = false end self end _ } end def_comparison_operator 'eq', '==' def_comparison_operator 'neq', '!=' def_comparison_operator 'lt', '<' def_comparison_operator 'gt', '>' def_comparison_operator 'le', '<=' def_comparison_operator 'ge', '>=' def_arithmetic_operator '+', '-', '*', '/', '%' class << self undef def_comparison_operator undef def_arithmetic_operator end def -@ if @value then @value = -@value.to_number else unarize @expr = expr('.to_number').unshift('-') end self end def logical_or(other) if @value and @value.true? then @value = ConstantTrue @unary = true @expr = @valuetype = nil else @expr = expr('.true?').push(' || ').concat(other.expr('.true?')) @valuetype = :ruby_boolean @unary = false end self end def logical_and(other) if @value and not @value.true? then @value = ConstantFalse @unary = true @expr = @valuetype = nil else @expr = expr('.true?').push(' && ').concat(other.expr('.true?')) @valuetype = :ruby_boolean @unary = false end self end def **(other) @expr = expr.push(' ** ').concat(other.expr) @valuetype = nil @unary = false self end def add_predicate(pred) unarize @expr = expr.concat(pred) @valuetype = nil self end def <<(other) path = other.expr path.shift # nil path.shift # .to_nodeset add_predicate path end def add_step(axis) add_predicate [ ".step(:#{axis.tr('-','_')})" ] end def expr(conv_method = nil) if @value then ret = @value.expr(conv_method) @value = nil elsif @valuetype == :ruby_boolean then ret = @expr unless conv_method == '.to_ruby' or conv_method == '.true?' then ret[0, 0] = [ nil, '.make_boolean(' ] ret.push ')' invoke_conv ret, conv_method unless conv_method == '.to_boolean' end elsif @valuetype == :number and conv_method == '.to_number' then ret = @expr elsif @valuetype == :string and conv_method == '.to_string' then ret = @expr elsif @valuetype == :boolean and conv_method == '.to_boolean' then ret = @expr else if conv_method then unarize invoke_conv @expr, conv_method end ret = @expr end @expr = :draff # for debug ret end end class LocationPath include CompilePhaseObject def initialize @root = false @steps = [] # [ axis, [ tests ], predicates ] end attr_reader :root, :steps protected :root, :steps def absolute! @root = true self end def add_step(axis, nodetype = false, localpart = false, namespace = false, predicate = nil) if nodetype == false and localpart == false and namespace == false then append_step axis, [], predicate else append_step axis, [ [ nodetype, localpart, namespace ] ], predicate end self end def <<(other) raise "BUG" if other.root other = other.steps other.each { |step| if step[0] then append_step(*step) else add_predicate(step[2]) end } self end def add_predicate(pred) @steps.push [ nil, nil, pred ] self end def **(other) unless other.is_a? LocationPath then ret = nil else othersteps = other.steps size = @steps.size unless size == othersteps.size then othersize = othersteps.size if size >= othersize then ret = (@steps[0, othersize] == othersize and self) else ret = (othersteps[0, size] == @steps and other) end else last = @steps.pop otherlast = othersteps.pop if @steps == othersteps and mix_step(last, otherlast) then ret = self else ret = nil end @steps.push last othersteps.push otherlast end end ret or Expression.new(expr) ** other end private UnifiableAxes = { 'descendant' => { 'descendant-or-self' => 'descendant', }, 'descendant-or-self' => { 'child' => 'descendant', 'descendant' => 'descendant', 'descendant-or-self' => 'descendant-or-self', }, 'ancestor' => { 'ancestor-or-self' => 'ancestor', }, 'ancestor-or-self' => { 'parent' => 'ancestor', 'ancestor' => 'ancestor', 'ancestor-or-self' => 'ancestor-or-self', }, 'following-sibling' => { 'following-sibling' => 'following-sibling', }, 'preceding-sibling' => { 'preceding-sibling' => 'preceding-sibling', }, 'following' => { 'following' => 'following', 'following-sibling' => 'following', }, 'preceding' => { 'preceding' => 'preceding', 'preceding-sibling' => 'preceding', }, 'child' => { 'following-sibling' => 'child', 'preceding-sibling' => 'child', }, } UnifiableAxes.default = {} def append_step(axis, test, predicate) lastaxis, lasttest, lastpred = laststep = @steps.last if axis == 'self' and test.empty? then @steps.push [ nil, nil, predicate ] if predicate elsif lastaxis and lasttest.empty? and not lastpred and not predicate and w = UnifiableAxes[lastaxis][axis] then laststep[0] = w laststep[1] = test else @steps.push [ axis, test, predicate ] end end def mix_step(step, other) if step[0] and step[0] == other[0] and step[2] == other[2] then step[1].concat other[1] step else nil end end public def expr(conv_method = nil) if @root then expr = [ nil, '.root_nodeset' ] else expr = [ nil, '.to_nodeset' ] end @steps.each { |axis,test,predicate| if axis.nil? then # predicate only expr.concat predicate elsif test.empty? and not predicate then expr.push ".select_all(:#{axis.tr('-','_')})" else expr.push ".step(:#{axis.tr('-','_')})" if test.empty? then expr.push ' { |n| n.select_all' else expr.push ' { |n| n.select { |i| ' test.each { |nodetype,localpart,namespace| if nodetype then expr.push "i.node_type == :#{nodetype.tr('-','_')}", ' && ' end if localpart then expr.push "i.name_localpart == #{localpart.dump}", ' && ' end if namespace.nil? then expr.push 'i.namespace_uri.nil?', ' && ' elsif namespace then namespace = namespace.dump expr.push('i.namespace_uri == ', nil, ".get_namespace(#{namespace})", ' && ') end expr[-1] = ' or ' } expr[-1] = ' }' end expr.concat predicate if predicate expr.push ' }' end } @steps = :draff # for debug invoke_conv expr, conv_method expr end def value_type nil end def value nil end def unarize self end def self.redirect_to_expr(*ops) ops.each { |op| name = op name = op[1..-1] if op[0] == ?. module_eval <<_, __FILE__, __LINE__ + 1 def #{name}(arg) ; Expression.new(expr) #{op} arg ; end _ } end redirect_to_expr('.eq', '.neq', '.lt', '.gt', '.le', '.ge', '+', '-', '*', '/', '%', '.logical_or', '.logical_and') class << self undef redirect_to_expr end def -@ -Expression.new(expr) end end Delim = '\\s\\(\\)\\[\\]\\.@,\\/\\|\\*\\+"\'=!<>:' Name = "[^-#{Delim}][^#{Delim}]*" Operator = { '@' => true, '::' => true, '(' => true, '[' => true, :MUL => true, 'and' => true, 'or' => true, 'mod' => true, 'div' => true, '/' => true, '//' => true, '|' => true, '+' => true, '-' => true, '=' => true, '!=' => true, '<' => true, '<=' => true, '>' => true, '>=' => true, ':' => false # ':' '*' => '*' must not be a MultiplyOperator # ':' 'and' => 'and' must be a OperatorName } NodeType = { 'comment' => true, 'text' => true, 'processing-instruction' => true, 'node' => true, } private def axis?(s) /\A[-a-zA-Z]+\z/ =~ s end def nodetype?(s) NodeType.key? s end def tokenize(src) token = [] src.scan(/(\.\.?|\/\/?|::?|!=|[<>]=?|[-()\[\].@,|+=*])| ("[^"]*"|'[^']*')|(\d+\.?\d*)| (\$?#{Name}(?::#{Name})?)| \s+|./ox) { |delim,literal,number,name| #/ if delim then if delim == '*' then delim = :MUL if (prev = token[-1]) and not Operator.key? prev[0] elsif delim == '::' then prev = token[-1] if prev and prev[0] == :Name and axis? prev[1] then prev[0] = :AxisName end elsif delim == '(' then if (prev = token[-1]) and prev[0] == :Name then if nodetype? prev[1] then prev[0] = :NodeType else prev[0] = :FuncName end end end token.push [ delim, delim ] elsif name then prev = token[-1] if name[0] == ?$ then name[0,1] = '' token.push [ :Variable, name ] elsif Operator.key? name and (prev = token[-1]) and not Operator[prev[0]] then token.push [ name, name ] else token.push [ :Name, name ] end elsif number then number << '.0' unless number.include? ?. token.push [ :Number, number ] elsif literal then literal.chop! literal[0,1] = '' token.push [ :Literal, literal ] else s = $&.strip token.push [ s, s ] unless s.empty? end } token end public def compile(src, pattern = false) @token = tokenize(src) @token.push [ false, :end ] @token.each { |i| p i } if @yydebug @token.reverse! @token.push [ :PATTERN, nil ] if pattern @context = 'context0' ret = do_parse ret = ret.unshift("proc { |context0| ").push(" }").join print ">>>>\n", ret, "\n<<<<\n" if @yydebug XPathProc.new eval(ret), src end def initialize(debug = false) super() @yydebug = debug end private def next_token @token.pop end def is_xpointer? false end def on_error(*args) # tok, val, values raise CompileError, 'parse error' end ---- header ---- # # xpath.rb : generated by racc # module XPath class Error < StandardError ; end class CompileError < Error ; end class TypeError < Error ; end class NameError < Error ; end class ArgumentError < Error ; end class InvalidOperation < Error ; end class XPathProc def initialize(proc, source) @proc = proc @source = source end attr_reader :source def call(context) @proc.call context end end def self.compile(src, pattern = false) @compiler = Compiler.new unless defined? @compiler @compiler.compile src, pattern end module XPathObject def _type type.name.sub(/\A.*::(?:XPath)?(?=[^:]+\z)/, '') end private :_type def type_error(into) raise XPath::TypeError, "failed to convert #{_type} into #{into}" end private :type_error def to_str # => to Ruby String type_error 'String' end def to_f # => to Ruby Float type_error 'Float' end def true? # => to Ruby Boolean type_error 'Boolean' end def to_ruby # => to Ruby Object self end def to_predicate # => to Ruby Float, true or false. shouldn't override. true? end def to_string(context) # => to XPath String. shouldn't override. context.make_string to_str end def to_number(context) # => to XPath Number. shouldn't override. context.make_number to_f end def to_boolean(context) # => to XPath Boolean. shouldn't override. context.make_boolean true? end public # called from compiled XPath expression def ==(other) if other.is_a? XPathNodeSet or other.is_a? XPathBoolean or other.is_a? XPathNumber then other == self else to_str == other.to_str end end def <(other) if other.is_a? XPathNodeSet then other > self else to_f < other.to_f end end def >(other) if other.is_a? XPathNodeSet then other < self else to_f > other.to_f end end def <=(other) if other.is_a? XPathNodeSet then other >= self else to_f <= other.to_f end end def >=(other) if other.is_a? XPathNodeSet then other <= self else to_f >= other.to_f end end def **(other) type_error 'NodeSet' end def predicate(&block) type_error 'NodeSet' end def at(pos) type_error 'NodeSet' end def funcall(name) # for XPointer raise XPath::NameError, "undefined function `#{name}' for #{_type}" end end class XPathBoolean include XPathObject class << self attr_reader :instance private :new end def to_str true?.to_s end # def to_f # def true? def to_ruby true? end def to_boolean(context) self end def ==(other) true? == other.true? end end class XPathTrueClass < XPathBoolean @instance = new def to_f 1.0 end def true? true end end class XPathFalseClass < XPathBoolean @instance = new def to_f 0.0 end def true? false end end XPathTrue = XPathTrueClass.instance XPathFalse = XPathFalseClass.instance class XPathNumber include XPathObject def initialize(num) raise ::TypeError, "must be a Float" unless num.is_a? Float @value = num end def to_str if @value.nan? then 'NaN' elsif @value.infinite? then if @value < 0 then '-Infinity' else 'Infinity' end else sprintf("%.100f", @value).gsub(/\.?0+\z/, '') # enough? end end def to_f @value end def true? @value != 0.0 and not @value.nan? end def to_ruby to_f end def to_predicate to_f end def to_number(context) self end def ==(other) if other.is_a? XPathNodeSet or other.is_a? XPathBoolean then other == self else @value == other.to_f end end def +(other) @value += other.to_f self end def -(other) @value -= other.to_f self end def *(other) @value *= other.to_f self end def /(other) @value /= other.to_f self end def %(other) n = other.to_f f = @value % n f = -f if @value < 0 f = -f if n < 0 @value = f self end def -@ @value = -@value self end def floor @value = @value.floor.to_f self end def ceil @value = @value.ceil.to_f self end def round f = @value unless f.nan? or f.infinite? then if f >= 0.0 then @value = f.round.to_f elsif f - f.truncate >= -0.5 then @value = f.ceil.to_f else @value = f.floor.to_f end end self end end class XPathString include XPathObject def initialize(str) raise ::TypeError, "must be a String" unless str.is_a? String @value = str end def to_str @value end def to_f if /\A\s*(-?\d+\.?\d*)(?:\s|\z)/ =~ @value then $1.to_f else 0.0 / 0.0 # NaN end end def true? not @value.empty? end def to_ruby to_str end def to_string(context) self end def concat(s) @value = @value + s self end def start_with?(s) /\A#{Regexp.quote(s)}/ =~ @value end def contain?(s) /#{Regexp.quote(s)}/ =~ @value end def substring_before(s) if /#{Regexp.quote(s)}/ =~ @value then @value = $` else @value = '' end self end def substring_after(s) if /#{Regexp.quote(s)}/ =~ @value then @value = $' else @value = '' end self end def substring(start, len) start = start.round.to_f if start.infinite? or start.nan? then @value = '' elsif len then len = len.round.to_f maxlen = start + len len = maxlen - 1.0 if len >= maxlen if start <= 1.0 then start = 0 else start = start.to_i - 1 end if len.nan? or len < 1.0 then @value = '' elsif len.infinite? then # @value = @value[start..-1] /\A[\W\w]{0,#{start}}/ =~ @value @value = $' else # @value = @value[start, len.to_i] /\A[\W\w]{0,#{start}}([\W\w]{0,#{len.to_i}})/ =~ @value @value = $1 end elsif start > 1.0 then # @value = @value[(start-1)..-1] /\A[\W\w]{0,#{start.to_i-1}}/ =~ @value @value = $' end raise "BUG" unless @value self end def size @value.gsub(/[^\Wa-zA-Z_\d]/, ' ').size end def normalize_space @value = @value.strip @value.gsub!(/\s+/, ' ') self end def translate(from, to) to = to.split(//) h = {} from.split(//).each_with_index { |i,n| h[i] = to[n] unless h.key? i } @value = @value.gsub(/[#{Regexp.quote(h.keys.join)}]/) { |s| h[s] } self end def replace(str) @value = str self end end ---- footer ---- # # Client NodeVisitor a NodeAdapter a Node # | | | | # |=| | | | # | |--{visit(node)}-->|=| | | # | | | |---{accept(self)}----------------->|=| # | | |=| | | | # | | | | | | # | | |=|<------------------{on_**(self)}---|=| # | | | | | | # | | | |--{wrap(node)}-->|=| | # | | | | | | | # | | | | |=| | # | |<--[NodeAdapter]--|=| | | # | | | | | # | |-----{request}----------------------->|=| | # | | | | |--{request}--->|=| # | | | | | | | # | | | | |<-----[Data]---|=| # | |<--------------------------[Data]-----|=| | # | | | | | # |=| | | | # | | | | # class TransparentNodeVisitor def visit(node) node end end class NullNodeAdapter def node self end def root nil end def parent nil end def children [] end def each_following_siblings end def each_preceding_siblings end def attributes [] end def namespaces [] end def index 0 end def node_type nil end def name_localpart nil end def qualified_name name_localpart end def namespace_uri nil end def string_value '' end def lang nil end def select_id(*ids) raise XPath::Error, "selection by ID is not supported" end end class AxisIterator def reverse_order? false end end class ReverseAxisIterator < AxisIterator def reverse_order? true end end class SelfIterator < AxisIterator def each(node, visitor) yield visitor.visit(node) end end class ChildIterator < AxisIterator def each(node, visitor, &block) visitor.visit(node).children.each { |i| yield visitor.visit(i) } end end class ParentIterator < AxisIterator def each(node, visitor) parent = visitor.visit(node).parent yield visitor.visit(parent) if parent end end class AncestorIterator < ReverseAxisIterator def each(node, visitor) node = visitor.visit(node).parent while node i = visitor.visit(node) parent = i.parent yield i node = parent end end end class AncestorOrSelfIterator < AncestorIterator def each(node, visitor) yield visitor.visit(node) super end end class DescendantIterator < AxisIterator def each(node, visitor) stack = visitor.visit(node).children.reverse while node = stack.pop i = visitor.visit(node) stack.concat i.children.reverse yield i end end end class DescendantOrSelfIterator < DescendantIterator def each(node, visitor) yield visitor.visit(node) super end end class FollowingSiblingIterator < AxisIterator def each(node, visitor) visitor.visit(node).each_following_siblings { |i| yield visitor.visit(i) } end end class PrecedingSiblingIterator < ReverseAxisIterator def each(node, visitor) visitor.visit(node).each_preceding_siblings { |i| yield visitor.visit(i) } end end class FollowingIterator < DescendantOrSelfIterator def each(node, visitor) while parent = (a = visitor.visit(node)).parent a.each_following_siblings { |i| super i, visitor } node = parent end end end class PrecedingIterator < ReverseAxisIterator def each(node, visitor) while parent = (adaptor = visitor.visit(node)).parent adaptor.each_preceding_siblings { |i| stack = visitor.visit(i).children.dup while node = stack.pop a = visitor.visit(node) stack.concat a.children yield a end yield visitor.visit(i) } node = parent end end end class AttributeIterator < AxisIterator def each(node, visitor) visitor.visit(node).attributes.each { |i| yield visitor.visit(i) } end end class NamespaceIterator < AxisIterator def each(node, visitor) visitor.visit(node).namespaces.each { |i| yield visitor.visit(i) } end end class XPathNodeSet class LocationStep < XPathNodeSet def initialize(context) @context = context @visitor = context.visitor @nodes = [] end def set_iterator(iterator) @iterator = iterator end def reuse(node) @node = node @nodes.clear end def select @iterator.each(@node, @visitor) { |i| node = i.node @nodes.push node if yield(i) } self end def select_all @iterator.each(@node, @visitor) { |i| @nodes.push i.node } self end end include XPathObject def initialize(context, *nodes) @context = context.dup @visitor = context.visitor nodes.sort! { |a,b| compare_position a, b } @nodes = nodes end attr_reader :nodes protected :nodes def to_str if @nodes.empty? then '' else @visitor.visit(@nodes[0]).string_value end end def to_f to_string(@context).to_f end def true? not @nodes.empty? end def to_ruby @nodes end def self.def_comparison_operator(*ops) ops.each { |op| module_eval <<_, __FILE__, __LINE__ + 1 def #{op}(other) if other.is_a? XPathBoolean then other #{op} self.to_boolean else visitor = @visitor str = @context.make_string('') ret = false @nodes.each { |node| str.replace visitor.visit(node).string_value break if ret = (other #{op} str) } ret end end _ } end def_comparison_operator '==', '<', '>', '<=', '>=' class << self undef def_comparison_operator end def **(other) super unless other.is_a? XPathNodeSet merge other.nodes self end def count @nodes.size end def first @nodes[0] end def each(&block) @nodes.each(&block) end def funcall(name) # for XPointer raise "BUG" unless block_given? func = ('f_' + name.tr('-', '_')).intern super unless respond_to? func, true size = @nodes.size pos = 1 c = @context.dup begin @nodes.collect! { |node| c.reuse node, pos, size pos += 1 args = yield(c) send(func, node, *args) } rescue Object::ArgumentError if $@[1] == "#{__FILE__}:#{__LINE__-3}:in `send'" then raise XPath::ArgumentError, "#{$!} for `#{name}'" end raise end self end private def compare_position(node1, node2) visitor = @visitor ancestors1 = [] ancestors2 = [] p1 = visitor.visit(node1).parent while p1 ancestors1.push node1 p1 = visitor.visit(node1 = p1).parent end p2 = visitor.visit(node2).parent while p2 ancestors2.push node2 p2 = visitor.visit(node2 = p2).parent end unless node1 == node2 then raise XPath::Error, "can't compare the positions of given two nodes" end n = -1 ancestors1.reverse_each { |node1| node2 = ancestors2[n] unless node1 == node2 then break unless node2 return visitor.visit(node1).index - visitor.visit(node2).index end n -= 1 } ancestors1.size - ancestors2.size end def merge(other) if @nodes.empty? or other.empty? then @nodes.concat other elsif (n = compare_position(@nodes.last, other.first)) <= 0 then @nodes.pop if n == 0 @nodes.concat other elsif (n = compare_position(other.last, @nodes.first)) <= 0 then other.pop if n == 0 @nodes = other.concat(@nodes) else newnodes = [] nodes = @nodes until nodes.empty? or other.empty? n = compare_position(nodes.last, other.last) if n > 0 then newnodes.push nodes.pop elsif n < 0 then newnodes.push other.pop else newnodes.push nodes.pop other.pop end end newnodes.reverse! @nodes.concat(other).concat(newnodes) end end IteratorForAxis = { :self => SelfIterator.new, :child => ChildIterator.new, :parent => ParentIterator.new, :ancestor => AncestorIterator.new, :ancestor_or_self => AncestorOrSelfIterator.new, :descendant => DescendantIterator.new, :descendant_or_self => DescendantOrSelfIterator.new, :following => FollowingIterator.new, :preceding => PrecedingIterator.new, :following_sibling => FollowingSiblingIterator.new, :preceding_sibling => PrecedingSiblingIterator.new, :attribute => AttributeIterator.new, :namespace => NamespaceIterator.new, } def get_iterator(axis) ret = IteratorForAxis[axis] unless ret then raise XPath::NameError, "invalid axis `#{axis.id2name.tr('_','-')}'" end ret end def make_location_step if defined? @__lstep__ then @__lstep__ else @__lstep__ = LocationStep.new(@context) end end public def step(axis) iterator = get_iterator(axis) lstep = make_location_step lstep.set_iterator iterator oldnodes = @nodes @nodes = [] oldnodes.each { |node| lstep.reuse node nodes = yield(lstep).nodes nodes.reverse! if iterator.reverse_order? merge nodes } self end def select_all(axis) iterator = get_iterator(axis) visitor = @visitor oldnodes = @nodes @nodes = [] oldnodes.each { |start| nodes = [] iterator.each(start, visitor) { |i| nodes.push i.node } nodes.reverse! if iterator.reverse_order? merge nodes } self end def predicate context = @context size = @nodes.size pos = 1 result = nil newnodes = @nodes.reject { |node| context.reuse node, pos, size pos += 1 result = yield(context) break if result.is_a? Numeric not result } if result.is_a? Numeric then at result else @nodes = newnodes end self end def at(pos) n = pos.to_i if n != pos or n <= 0 then node = nil else node = @nodes[n - 1] end @nodes.clear @nodes.push node if node self end end class Context def initialize(node, namespace = nil, variable = nil, visitor = nil) visitor = TransparentNodeVisitor.new unless visitor @visitor = visitor @node = node @context_position = 1 @context_size = 1 @variables = variable @namespaces = namespace || {} end attr_reader :visitor, :node, :context_position, :context_size def reuse(node, pos = 1, size = 1) @variables = nil @node, @context_position, @context_size = node, pos, size end def get_variable(name) value = @variables && @variables[name] # value should be a XPathObjcect. raise XPath::NameError, "undefined variable `#{name}'" unless value value end PredefinedNamespace = { 'xml' => 'http://www.w3.org/XML/1998/namespace', } def get_namespace(prefix) ret = @namespaces[prefix] || PredefinedNamespace[prefix] raise XPath::Error, "undeclared namespace `#{prefix}'" unless ret ret end def make_string(str) XPathString.new str end def make_number(num) XPathNumber.new num end def make_boolean(f) if f then XPathTrue else XPathFalse end end def make_nodeset(*nodes) XPathNodeSet.new(self, *nodes) end def to_nodeset make_nodeset @node end def root_nodeset make_nodeset @visitor.visit(@node).root end def funcall(name, *args) begin send('f_' + name.tr('-', '_'), *args) rescue Object::NameError if $@[0] == "#{__FILE__}:#{__LINE__-2}:in `send'" then raise XPath::NameError, "undefined function `#{name}'" end raise rescue Object::ArgumentError if $@[1] == "#{__FILE__}:#{__LINE__-7}:in `send'" then raise XPath::ArgumentError, "#{$!} for `#{name}'" end raise end end private def must(type, *args) args.each { |i| unless i.is_a? type then s = type.name.sub(/\A.*::(?:XPath)?(?=[^:]+\z)/, '') raise XPath::TypeError, "argument must be #{s}" end } end def must_be_nodeset(*args) must XPathNodeSet, *args end def f_last make_number @context_size.to_f end def f_position make_number @context_position.to_f end def f_count(nodeset) must_be_nodeset nodeset make_number nodeset.count.to_f end def f_id(obj) unless obj.is_a? XPathNodeSet then ids = obj.to_str.strip.split(/\s+/) else ids = [] obj.each { |node| ids.push @visitor.visit(node).string_value } end root = @visitor.visit(@node).root make_nodeset(*@visitor.visit(root).select_id(*ids)) end def f_local_name(nodeset = nil) unless nodeset then n = @node else must_be_nodeset nodeset n = nodeset.first end n = @visitor.visit(n) if n n = n.name_localpart if n n = '' unless n make_string n end def f_namespace_uri(nodeset = nil) unless nodeset then n = @node else must_be_nodeset nodeset n = nodeset.first end n = @visitor.visit(n) if n n = n.namespace_uri if n n = '' unless n make_string n end def f_name(nodeset = nil) unless nodeset then n = @node else must_be_nodeset nodeset n = nodeset.first end n = @visitor.visit(n) if n n = n.qualified_name if n n = '' unless n make_string n end def f_string(obj = nil) obj = to_nodeset unless obj obj.to_string self end def f_concat(str, str2, *strs) s = str2.to_str.dup strs.each { |i| s << i.to_str } str.to_string(self).concat(s) end def f_starts_with(str, sub) make_boolean str.to_string(self).start_with?(sub.to_str) end def f_contains(str, sub) make_boolean str.to_string(self).contain?(sub.to_str) end def f_substring_before(str, sub) str.to_string(self).substring_before sub.to_str end def f_substring_after(str, sub) str.to_string(self).substring_after sub.to_str end def f_substring(str, start, len = nil) len = len.to_number(self) if len str.to_string(self).substring start.to_number(self), len end def f_string_length(str = nil) if str then str = str.to_string(self) else str = make_string(@node.string_value) end make_number str.size.to_f end def f_normalize_space(str = nil) if str then str = str.to_string(self) else str = make_string(@node.string_value) end str.normalize_space end def f_translate(str, from, to) str.to_string(self).translate from.to_str, to.to_str end def f_boolean(obj) obj.to_boolean self end def f_not(bool) make_boolean(!bool.true?) end def f_true make_boolean true end def f_false make_boolean false end def f_lang(str) lang = @visitor.visit(@node).lang make_boolean(lang && /\A#{Regexp.quote(str.to_str)}(?:-|\z)/i =~ lang) end def f_number(obj = nil) obj = to_nodeset unless obj obj.to_number self end def f_sum(nodeset) must_be_nodeset nodeset sum = 0.0 nodeset.each { |node| sum += make_string(@visitor.visit(node).string_value).to_f } make_number sum end def f_floor(num) num.to_number(self).floor end def f_ceiling(num) num.to_number(self).ceil end def f_round(num) num.to_number(self).round end end end xmlscan-0.0.10/xpathtree.rb0100644000175000017500000002035607222533362014653 0ustar katsuusers# # xpathtree.rb # # Copyright (C) Ueno Katsuhiro 2000 # # $Id: xpathtree.rb,v 1.6 2000/12/28 03:43:46 katsu Exp $ # require 'xmlscan' require 'xpath' module XPath # obediently implementation of XPath data model module DataModel class Node < NullNodeAdapter def initialize @parent = nil # @index = 0 end attr_accessor :parent protected :parent= # attr_accessor :index # protected :index= def append_child raise "can't take any children" end def node self end def root (p = @parent) and p.root end def lang (p = @parent) and p.lang end def each_following_siblings if @parent then nodes = @parent.children n = nodes.index(self) raise "BUG" unless n (n + 1).upto(nodes.size - 1) { |i| yield nodes[i] } end end def each_preceding_siblings if @parent then nodes = @parent.children n = nodes.index(self) raise "BUG" unless n (n - 1).downto(0) { |i| yield nodes[i] } end end def index unless @parent then 0 else n = @parent.children.index(self) raise "BUG" unless n n end end def namespace_decls nil end def traverse(level = 0, &block) block.call level, self children.each { |node| node.traverse(level + 1, &block) } end def abs_index indices = [] node = self while node indices.push node.index node = node.parent end indices.reverse! indices.join(':') end def inspect uri = namespace_uri qname = qualified_name strval = string_value dst = "<#{node_type.id2name.capitalize} #{abs_index}" dst << " qname=#{qname.dump}" if qname dst << " ns=#{uri.dump}" if uri dst << " str=#{strval.inspect}" if strval dst << ">" end end class ParentNode < Node def initialize @children = [] super end attr_reader :children def append_child(node) # node.index = @children.size @children.push node node.parent = self end def string_value @children.collect{ |i| i.string_value }.join end end class RootNode < ParentNode private :parent= def root self end def node_type :root end end module NamedNode def initialize(namespace_uri, prefix, localpart) @namespace_uri = namespace_uri @name_prefix, @name_localpart = prefix, localpart super() end attr_reader :namespace_uri, :name_localpart def qualified_name if @name_prefix then @name_prefix + ':' + @name_localpart else @name_localpart end end end class ElementNode < ParentNode include NamedNode def initialize(namespace_uri, prefix, localpart, attrs, namespaces) @namespaces = namespaces @attrnodes = make_attr_nodes(attrs) @namespacenodes = make_namespace_nodes(namespaces) super namespace_uri, prefix, localpart end private def make_namespace_nodes(namespaces) dst = [] namespaces.each { |k,v| node = NamespaceNode.new(k, v) node.parent = self dst.push node } dst end def make_attr_nodes(attrs) attrs.collect { |uri,prefix,name,val| node = AttributeNode.new(uri, prefix, name, val) node.parent = self node } end public def node_type :element end def lang lang = nil @attrnodes.each { |node| if node.name_localpart == 'lang' and node.namespace_uri == 'http://www.w3.org/XML/1998/namespace' then lang = node.string_value break end } lang or super end def attributes @attrnodes end def namespaces @namespacenodes end def namespace_decls @namespaces end def traverse(level = 0, &block) block.call level, self namespaces.each { |node| node.traverse(level + 1, &block) } attributes.each { |node| node.traverse(level + 1, &block) } children.each { |node| node.traverse(level + 1, &block) } end end class NamespaceNode < Node def initialize(prefix, uri) @name_localpart, @string_value = prefix, uri super() end attr_reader :name_localpart, :string_value def node_type :namespace end def index a = @parent.namespaces a.index(self) - a.size - @parent.attributes.size end end class AttributeNode < Node include NamedNode def initialize(namespace_uri, prefix, localpart, value) @string_value = value super namespace_uri, prefix, localpart end attr_reader :string_value def node_type :attribute end def index a = @parent.attributes a.index(self) - a.size end def namespace_decls @parent.namespace_decls end end class TextNode < Node def initialize(text) @string_value = text super() end attr_reader :string_value def node_type :text end end class CommentNode < TextNode def node_type :comment end end class PINode < TextNode def initialize(target, pi) @name_localpart = target super pi end attr_reader :name_localpart def node_type :processing_instruction end end class Builder < XMLScannerWithNamespace private def on_text(str) node = TextNode.new(str) @nodestack[-1].append_child node end def on_comment(strs) node = CommentNode.new(strs.join) @nodestack[-1].append_child node end def on_pi(target, pi) super node = PINode.new(target, pi) @nodestack[-1].append_child node end def expand_attr_namespace(attr) dst = [] attr.each { |key,val| namespace, prefix, name = expand_qualified_name(key) dst.push [ namespace, prefix, name, val ] } dst end def on_start_element(name, attr) namespace, prefix, name = name namespaces = @elemstack.namespace.dup namespaces.delete :default node = ElementNode.new(namespace, prefix, name, attr, namespaces) @nodestack[-1].append_child node @nodestack.push node end def on_end_element(name) @nodestack.pop end public undef step def parse(*args) @root = RootNode.new @nodestack = [ @root ] super @root end end end end if $0 == __FILE__ then require 'readline' STDOUT.sync = STDERR.sync = true module XPath @compiler = Compiler.new(true) if $DEBUG end raise "requires 1 argument" unless ARGV[0] STDERR.print "parsing #{ARGV[0]} ... " root = XPath::DataModel::Builder.new.parse(File.open(ARGV[0])) STDERR.print "done.\n" context = XPath::Context.new(root) if $DEBUG then root.traverse { |level,node| print ' ' * level, node.inspect, "\n" } end while src = Readline.readline("xpath:#{context.node.abs_index}> ", true) cd = (/\Acd\s+/ =~ src) src = $' if cd begin t1 = Time.times.utime proc = XPath.compile(src) t2 = Time.times.utime result = proc.call(context) t3 = Time.times.utime if result.is_a? Array and not result.empty? then result.each { |i| print i.inspect, "\n" } if cd then node = result[0] context = XPath::Context.new(node, node.namespace_decls) end else p result end printf "compile: %.2f sec eval: %.2f sec\n", t2 - t1, t3 - t2 rescue XPath::Error at = $@ printf "%s: %s (%s)\n", at.shift, $!, $!.type at.each { |i| print "\tfrom ", i, "\n" } end end end xmlscan-0.0.10/README0100644000175000017500000000544407225067604013207 0ustar katsuusers xmlscan ¤Ï XML ´ØÏ¢µ»½Ñ¤ò Ruby ¤Ç¼ÂÁõ¤·¤¿¤â¤Î¤Î´ó¤»½¸¤á¤Ç¤¹¡£ Ruby ¤À¤±¤Ç XML ¥¢¥×¥ê¥±¡¼¥·¥ç¥ó¤ò¹½ÃۤǤ­¤ë¤è¤¦¤Ë¤¹¤ë¤Î¤¬ÌÜɸ¤Ç¤¹¡£ ¿§¡¹½ñ¤­¤«¤±¤Ç¥´¥Á¥ã¥´¥Á¥ã¤·¤Æ¤Þ¤¹¤¬¡£ xmlscan.rb: class XMLScanner XML ʸ½ñ¤ò XML ¤ÎʸˡÍ×ÁÇ (¥¿¥°¤È¤«) ¤ËÀÚ¤êʬ¤±¤ë¥¯¥é¥¹¡£ ¥é¥¤¥Ö¥é¥ê¤Ë°ìÀÚÍê¤Ã¤Æ¤¤¤Ê¤¤¤Î¤Ç Ruby ¤À¤±¤¢¤ì¤Ðư¤­¤Þ¤¹¡£ module XMLScanner::Loose ¥¨¥é¡¼¤ò̵»ë¤·¤Æ²òÀϤò³¹Ô¤µ¤»¤ë¤è¤¦¤Ë¤¹¤ë Mix in¡£ module XMLScanner::Recoverable ¥¨¥é¡¼Êó¹ð (raise ParseError) ¸å¤â²òÀϤò³¹Ô¤Ç¤­¤ë¤è¤¦¤Ë¤¹¤ë Mix in¡£ module XMLScanner::ExternalEntity ³°Éô²òÀÏÂоݼÂÂΤò²òÀϤ¹¤ë¤¿¤á¤Î Mix in¡£ class LooseXMLScanner class RecoverableXMLScanner XMLScanner ¤Ë {Loose|Recoverable} ¤ò include ¤·¤¿¤â¤Î¡£ class WellFormedXMLScanner XMLScanner ¤Ë¤¤¤¯¤Ä¤«¤ÎÀ°·Á¼°À©Ìó¤ò²Ã¤¨¤¿¥¯¥é¥¹¡£ class XMLScannerWithNamespace XML Namespace ¤ËÂбþ¤·¤¿ WellFormedXMLScanner¡£ htmlscan.rb: module XMLScanner::HTML HTML ¤ò²òÀϤ¹¤ë¤¿¤á¤Î Mix in¡£ class HTMLScanner class LooseHTMLScanner class RecoverableHTMLScanner XMLScanner ¤Ë HTML ¤ò include ¤·¤¿¤â¤Î¡£ ¼ê¸µ¤Î proxy ¤Ë»Ä¤Ã¤Æ¤¤¤¿ HTML ʸ½ñ¤ò»È¤Ã¤Æ²òÀÏ»þ´Ö¤ò·×¤Ã¤Æ¤ß¤ë¤È html-parser/sgml-parser.rb ¤è¤ê¾¯¤·Â®¤¤¤ß¤¿¤¤¤Ç¤¹¡£ total total bytes lines total msec scanner files size lines /file /file seconds /file at once (read): SGMLParser 8631 92260346 1937688 10689.4 224.5 3325.64 385.31 LooseHTMLScanner 8631 92260346 1937688 10689.4 224.5 2247.90 260.44 SGMLParser (max) -- -- 453969 15215 -- 54380.00 LooseHTMLScanner (max) -- -- 453969 15215 -- 11860.00 by line (gets): SGMLParser 8631 92260346 1937688 10689.4 224.5 3595.33 416.56 LooseHTMLScanner 8631 92260346 1937688 10689.4 224.5 2867.39 332.22 SGMLParser (max) -- -- 453969 15215 -- 20080.00 LooseHTMLScanner (max) -- -- 453969 15215 -- 15210.00 xmltoken.rb: module XMLScanner::Tokenizer pull ·¿¤Ê XMLScanner ¤È¤Ç¤â¤¤¤¦¤«¡£Â¿Ê¬¤Þ¤ÀÅÓÃæ¡£ xmldtd.ry: class XMLDTDScanner DTD ¤ò²òÀϤ¹¤ë¤¿¤á¤Î¥¯¥é¥¹¡£Í× racc¡£½ñ¤­¤«¤±¡£ xpath.ry: module XPath Ruby ¤Ê XPath ¥×¥í¥»¥Ã¥µ¡£ class XPath::Compiler XPath ¼°¤ò Ruby ¥¹¥¯¥ê¥×¥È¤Ë¥³¥ó¥Ñ¥¤¥ë¤¹¤ë¤¿¤á¤Î¥¯¥é¥¹¡£Í× racc¡£ xpath.rb: racc -E -o xpath.rb xpath.ry xpathtree.rb: XPath ¥Ç¡¼¥¿¥â¥Ç¥ë¤Î¼ÂÁõ¡£¤¢¤¯¤Þ¤Ç¤âÎã¡¢¤Î¤Ä¤â¤ê¡£ xpath-dom.rb: DOM Âбþ XPath¡£½èÍýÉô¤È¥Ç¡¼¥¿¹½Â¤¤ÎʬΥ¤Ë Adapter ¥Ñ¥¿¡¼¥ó¤ò »È¤Ã¤Æ¤ß¤¿¤ó¤Ç¤¹¤¬¡¢¼ÂºÝư¤«¤·¤Æ¤ß¤ë¤È¤«¤Ê¤êÃÙ¤¤¤Ç¤¹ (;_; ¥Ü¥Ä¤ä¤Ê¡¢¤³¤ê¤ã¡£ ÇÛÉÛ¾ò·ï¤Ï Ruby ¤ÈƱ¤¸¤Ç¤¹¡£ -=====--===- ¤¦¤¨¤Î ¤«¤Ä¤Ò¤í @ BLUE-SKYNET -== ---=----===- http://www.blue.sky.or.jp/ -== xmlscan-0.0.10/analize.rb0100644000175000017500000000552207225067604014274 0ustar katsuusers#!/usr/bin/ruby class Array def sum sum = 0 each { |i| sum += i } sum end end entries = gets.chomp.split("\t") columns = gets.chomp.split("\t") @sizes = [] @lines = [] records = columns.collect { entries.collect { [] } } record_col = [] entries.size.times { |n| records.each { |a| record_col.push a[n] } } while gets file, size, line, *record = split("\t") unless record.size == record_col.size then STDERR.print "parse error\n" break end @sizes.push size.to_i @lines.push line.to_i record.each_with_index { |i,n| if /\A\d/ =~ i then record_col[n].push i.to_f else record_col[n].push nil end } end print <<_ total total bytes lines total msec scanner files size lines /file /file seconds /file _ #------------------- ----- --------- ------- -------.- -----.- -----.-- ----.-- total_size = @sizes.sum total_lines = @lines.sum printf(" %-25s -- -- %7d %5d -- --\n", '-- (min)', @sizes.min, @lines.min) printf(" %-19s %5d %9d %7d %9.1f %7.1f %8s %7s\n", '--', @sizes.size, total_size, total_lines, total_size.to_f / @sizes.size, total_lines.to_f / @sizes.size, '--', '--') printf(" %-25s -- -- %7d %5d -- --\n\n", '-- (max)', @sizes.max, @lines.max) def summarize(scanner, record, others = []) files = 0 total_size = total_lines = total_seconds = 0 max_size = max_lines = max_seconds = 0 min_size = min_lines = min_seconds = nil record.each_with_index { |sec,n| next if sec.nil? or others.find { |a| a[n].nil? } files += 1 size, line = @sizes[n], @lines[n] total_size += size total_lines += line total_seconds += sec max_size = size if max_size < size max_lines = line if max_lines < line max_seconds = sec if max_seconds < sec min_size = size unless min_size and min_size <= size min_lines = line unless min_lines and min_lines <= line min_seconds = sec unless min_seconds and min_seconds <= sec } printf(" %-25s -- -- %7d %5d -- %7.2f\n", scanner + ' (min)', min_size.to_i, min_lines.to_i, (min_seconds or 0) * 1000.0) printf(" %-19s %5d %9d %7d %9.1f %7.1f %8.2f %7.2f\n", scanner, files, total_size, total_lines, total_size.to_f / files, total_lines.to_f / files, total_seconds, total_seconds * 1000.0 / files) printf(" %-25s -- -- %7d %5d -- %7.2f\n\n", scanner + ' (max)', max_size, max_lines, max_seconds * 1000.0) end columns.each_with_index { |col,cn| print col, ":\n\n" entries.each_with_index { |i,n| summarize i, records[cn][n] } print col, " (commonly succeeded):\n\n" entries.each_with_index { |i,n| summarize i, records[cn][n], records[cn] } } xmlscan-0.0.10/benchmark.rb0100644000175000017500000000225207225067604014600 0ustar katsuusers#!/usr/bin/ruby $:.push './html-parser' require 'htmlscan' require 'sgml-parser' class LooseHTMLScanner def parse_ary(a) parse a end end class SGMLParser alias parse feed def parse_ary(a) for i in a feed i end end end Entries = [ SGMLParser, LooseHTMLScanner ] require 'nkf' #$KCODE = 'E' def benchmark(klass, method, arg) begin scanner = klass.new t1 = Time.times.utime scanner.send method, arg t2 = Time.times.utime sprintf "%.2f", t2 - t1 rescue Exception raise if $!.is_a? Interrupt sprintf "=%s=", $!.type end end if ARGV[0] == '-f' then files = File.open(ARGV[1]).readlines files.each { |i| i.chomp! } else files = ARGV end STDOUT.sync = true print Entries.collect{ |i| i.name }.join("\t"), "\n" print "at once (read)\tby line (gets)\n" files.each { |i| print i src = File.open(i.chomp) { |f| s = f.gets("\r\n\r\n") # skip http header f.read or s } src = NKF.nkf('-dexm0', src) print "\t", src.size src_a = src.to_a print "\t", src_a.size Entries.each { |klass| print "\t", benchmark(klass, :parse, src) print "\t", benchmark(klass, :parse_ary, src_a) } print "\n" } xmlscan-0.0.10/xmldtd.rb0100644000175000017500000000037307225067604014144 0ustar katsuusersif not File.exist?('xmldtd.tab.rb') or File.mtime('xmldtd.ry') > File.mtime('xmldtd.tab.rb') then STDERR.print "racc xmldtd.ry ...\n" system "racc -v -g -c xmldtd.ry" exit $? if $? != 0 STDERR.print "racc done.\n" end load 'xmldtd.tab.rb' xmlscan-0.0.10/xpath.rb0100644000175000017500000022744507225067613014007 0ustar katsuusers# # xpath.rb: generated by racc (runtime embedded) # ###### racc/parser.rb unless $".index 'racc/parser.rb' then $".push 'racc/parser.rb' type.module_eval <<'..end /home/katsu/local/lib/site_ruby/racc/parser.rb modeval..id92db944ac5', '/home/katsu/local/lib/site_ruby/racc/parser.rb', 1 # # parser.rb # # Copyright (c) 1999,2000 Minero Aoki # # This program is free software. # You can distribute/modify this program under the terms of # the GNU Lesser General Public License version 2 or later. # # As a special exception, when this code is copied by Racc # into a Racc output file, you may use that output file # without restriction. # module Racc class ParseError < StandardError; end end unless defined? ParseError then ParseError = Racc::ParseError end module Racc class Parser private begin if defined? Racc_Debug_Ruby_Parser then raise LoadError, 'debug ruby routine' end require 'racc/cparse' unless new.respond_to? :_racc_do_parse_c, true then raise LoadError, 'old cparse.so' end Racc_Main_Parsing_Routine = :_racc_do_parse_c Racc_YY_Parse_Method = :_racc_yyparse_c rescue LoadError Racc_Main_Parsing_Routine = :_racc_do_parse_rb Racc_YY_Parse_Method = :_racc_yyparse_rb end Racc_ruby_parser_version = '1.3.3' Racc_parser_version = Racc_ruby_parser_version def self.racc_runtime_type if Racc_Main_Parsing_Routine == :_racc_do_parse_c then 'c' else 'ruby' end end def _racc_setup t = self.type unless t::Racc_debug_parser then @yydebug = false end @yydebug = false unless defined? @yydebug if @yydebug then @racc_debug_out = $stderr unless defined? @racc_debug_out @racc_debug_out ||= $stderr end arg = t::Racc_arg if arg.size < 14 then arg[13] = true end arg end def _racc_init_sysvars @racc_state = [ 0 ] @racc_tstack = [] @racc_vstack = [] @racc_t = nil @racc_val = nil @racc_read_next = true @racc_user_yyerror = false @racc_error_status = 0 end ### ### do_parse ### def do_parse __send__ Racc_Main_Parsing_Routine, _racc_setup(), false end def next_token raise NotImplementError, "#{self.type}\#next_token must be defined" end def _racc_do_parse_rb( arg, in_debug ) action_table, action_check, action_default, action_pointer, goto_table, goto_check, goto_default, goto_pointer, nt_base, reduce_table, token_table, shift_n, reduce_n, use_result = arg _racc_init_sysvars act = i = nil nerr = 0 catch( :racc_end_parse ) { while true do if i = action_pointer[ @racc_state[-1] ] then if @racc_read_next then if @racc_t != 0 then # $ tok, @racc_val = next_token() @racc_t = (token_table[tok] or 1) # error token racc_read_token( @racc_t, tok, @racc_val ) if @yydebug @racc_read_next = false end end i += @racc_t if i >= 0 and act = action_table[i] and action_check[i] == @racc_state[-1] then ; else act = action_default[ @racc_state[-1] ] end else act = action_default[ @racc_state[-1] ] end while act = _racc_evalact( act, arg ) do end end } end ### ### yyparse ### def yyparse( recv, mid ) __send__ Racc_YY_Parse_Method, recv, mid, _racc_setup(), true end def _racc_yyparse_rb( recv, mid, arg, c_debug ) action_table, action_check, action_default, action_pointer, goto_table, goto_check, goto_default, goto_pointer, nt_base, reduce_table, token_table, shift_n, reduce_n, use_result, = arg _racc_init_sysvars tok = nil act = nil i = nil nerr = 0 catch( :racc_end_parse ) { until i = action_pointer[ @racc_state[-1] ] do while act = _racc_evalact( action_default[ @racc_state[-1] ], arg ) do end end recv.__send__( mid ) do |tok, val| # $stderr.puts "rd: tok=#{tok}, val=#{val}" @racc_val = val @racc_t = (token_table[tok] or 1) # error token @racc_read_next = false i += @racc_t if i >= 0 and act = action_table[i] and action_check[i] == @racc_state[-1] then # $stderr.puts "01: act=#{act}" else act = action_default[ @racc_state[-1] ] # $stderr.puts "02: act=#{act}" # $stderr.puts "curstate=#{@racc_state[-1]}" end while act = _racc_evalact( act, arg ) do end while not (i = action_pointer[ @racc_state[-1] ]) or not @racc_read_next or @racc_t == 0 do # $ if i and i += @racc_t and i >= 0 and act = action_table[i] and action_check[i] == @racc_state[-1] then # $stderr.puts "03: act=#{act}" ; else # $stderr.puts "04: act=#{act}" act = action_default[ @racc_state[-1] ] end while act = _racc_evalact( act, arg ) do end end end } end ### ### common ### def _racc_evalact( act, arg ) # $stderr.puts "ea: act=#{act}" action_table, action_check, action_default, action_pointer, goto_table, goto_check, goto_default, goto_pointer, nt_base, reduce_table, token_table, shift_n, reduce_n, use_result, = arg nerr = 0 # tmp if act > 0 and act < shift_n then # # shift # if @racc_error_status > 0 then @racc_error_status -= 1 unless @racc_t == 1 # error token end @racc_vstack.push @racc_val @racc_state.push act @racc_read_next = true if @yydebug then @racc_tstack.push @racc_t racc_shift( @racc_t, @racc_tstack, @racc_vstack ) end elsif act < 0 and act > -reduce_n then # # reduce # code = catch( :racc_jump ) { @racc_state.push _racc_do_reduce( arg, act ) false } if code then case code when 1 # yyerror @racc_user_yyerror = true # user_yyerror return -reduce_n when 2 # yyaccept return shift_n else raise RuntimeError, '[Racc Bug] unknown jump code' end end elsif act == shift_n then # # accept # racc_accept if @yydebug throw :racc_end_parse, @racc_vstack[0] elsif act == -reduce_n then # # error # case @racc_error_status when 0 unless arg[21] then # user_yyerror nerr += 1 on_error @racc_t, @racc_val, @racc_vstack end when 3 if @racc_t == 0 then # is $ throw :racc_end_parse, nil end @racc_read_next = true end @racc_user_yyerror = false @racc_error_status = 3 while true do if i = action_pointer[ @racc_state[-1] ] then i += 1 # error token if i >= 0 and (act = action_table[i]) and action_check[i] == @racc_state[-1] then break end end throw :racc_end_parse, nil if @racc_state.size < 2 @racc_state.pop @racc_vstack.pop if @yydebug then @racc_tstack.pop racc_e_pop( @racc_state, @racc_tstack, @racc_vstack ) end end return act else raise RuntimeError, "[Racc Bug] unknown action #{act.inspect}" end racc_next_state( @racc_state[-1], @racc_state ) if @yydebug nil end def _racc_do_reduce( arg, act ) action_table, action_check, action_default, action_pointer, goto_table, goto_check, goto_default, goto_pointer, nt_base, reduce_table, token_table, shift_n, reduce_n, use_result, = arg state = @racc_state vstack = @racc_vstack tstack = @racc_tstack i = act * -3 len = reduce_table[i] reduce_to = reduce_table[i+1] method_id = reduce_table[i+2] void_array = [] tmp_t = tstack[ -len, len ] if @yydebug tmp_v = vstack[ -len, len ] tstack[ -len, len ] = void_array if @yydebug vstack[ -len, len ] = void_array state[ -len, len ] = void_array # tstack must be updated AFTER method call if use_result then vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0]) else vstack.push __send__(method_id, tmp_v, vstack) end tstack.push reduce_to racc_reduce( tmp_t, reduce_to, tstack, vstack ) if @yydebug k1 = reduce_to - nt_base if i = goto_pointer[ k1 ] then i += state[-1] if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1 then return curstate end end goto_default[ k1 ] end def on_error( t, val, vstack ) raise ParseError, "\nparse error on value #{val.inspect}" end def yyerror throw :racc_jump, 1 end def yyaccept throw :racc_jump, 2 end def yyerrok @racc_error_status = 0 end # for debugging output def racc_read_token( t, tok, val ) @racc_debug_out.print 'read ' @racc_debug_out.print tok.inspect, '(internaly ', racc_token2str(t), ') ' @racc_debug_out.puts val.inspect @racc_debug_out.puts end def racc_shift( tok, tstack, vstack ) @racc_debug_out.puts "shift #{racc_token2str tok}" racc_print_stacks tstack, vstack @racc_debug_out.puts end def racc_reduce( toks, sim, tstack, vstack ) out = @racc_debug_out out.print 'reduce ' if toks.empty? then out.print ' ' else toks.each {|t| out.print ' ', racc_token2str(t) } end out.puts " --> #{racc_token2str(sim)}" racc_print_stacks tstack, vstack @racc_debug_out.puts end def racc_accept @racc_debug_out.puts 'accept' @racc_debug_out.puts end def racc_e_pop( state, tstack, vstack ) @racc_debug_out.puts 'error recovering mode: pop token' racc_print_states state racc_print_stacks tstack, vstack @racc_debug_out.puts end def racc_next_state( curstate, state ) @racc_debug_out.puts "goto #{curstate}" racc_print_states state @racc_debug_out.puts end def racc_print_stacks( t, v ) out = @racc_debug_out out.print ' [' t.each_index do |i| out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')' end out.puts ' ]' end def racc_print_states( s ) out = @racc_debug_out out.print ' [' s.each {|st| out.print ' ', st } out.puts ' ]' end def racc_token2str( tok ) type::Racc_token_to_s_table[tok] or raise RuntimeError, "[Racc Bug] can't convert token #{tok} to string" end end end ..end /home/katsu/local/lib/site_ruby/racc/parser.rb modeval..id92db944ac5 end # end of racc/parser.rb # # xpath.rb : generated by racc # module XPath class Error < StandardError ; end class CompileError < Error ; end class TypeError < Error ; end class NameError < Error ; end class ArgumentError < Error ; end class InvalidOperation < Error ; end class XPathProc def initialize(proc, source) @proc = proc @source = source end attr_reader :source def call(context) @proc.call context end end def self.compile(src, pattern = false) @compiler = Compiler.new unless defined? @compiler @compiler.compile src, pattern end module XPathObject def _type type.name.sub(/\A.*::(?:XPath)?(?=[^:]+\z)/, '') end private :_type def type_error(into) raise XPath::TypeError, "failed to convert #{_type} into #{into}" end private :type_error def to_str # => to Ruby String type_error 'String' end def to_f # => to Ruby Float type_error 'Float' end def true? # => to Ruby Boolean type_error 'Boolean' end def to_ruby # => to Ruby Object self end def to_predicate # => to Ruby Float, true or false. shouldn't override. true? end def to_string(context) # => to XPath String. shouldn't override. context.make_string to_str end def to_number(context) # => to XPath Number. shouldn't override. context.make_number to_f end def to_boolean(context) # => to XPath Boolean. shouldn't override. context.make_boolean true? end public # called from compiled XPath expression def ==(other) if other.is_a? XPathNodeSet or other.is_a? XPathBoolean or other.is_a? XPathNumber then other == self else to_str == other.to_str end end def <(other) if other.is_a? XPathNodeSet then other > self else to_f < other.to_f end end def >(other) if other.is_a? XPathNodeSet then other < self else to_f > other.to_f end end def <=(other) if other.is_a? XPathNodeSet then other >= self else to_f <= other.to_f end end def >=(other) if other.is_a? XPathNodeSet then other <= self else to_f >= other.to_f end end def **(other) type_error 'NodeSet' end def predicate(&block) type_error 'NodeSet' end def at(pos) type_error 'NodeSet' end def funcall(name) # for XPointer raise XPath::NameError, "undefined function `#{name}' for #{_type}" end end class XPathBoolean include XPathObject class << self attr_reader :instance private :new end def to_str true?.to_s end # def to_f # def true? def to_ruby true? end def to_boolean(context) self end def ==(other) true? == other.true? end end class XPathTrueClass < XPathBoolean @instance = new def to_f 1.0 end def true? true end end class XPathFalseClass < XPathBoolean @instance = new def to_f 0.0 end def true? false end end XPathTrue = XPathTrueClass.instance XPathFalse = XPathFalseClass.instance class XPathNumber include XPathObject def initialize(num) raise ::TypeError, "must be a Float" unless num.is_a? Float @value = num end def to_str if @value.nan? then 'NaN' elsif @value.infinite? then if @value < 0 then '-Infinity' else 'Infinity' end else sprintf("%.100f", @value).gsub(/\.?0+\z/, '') # enough? end end def to_f @value end def true? @value != 0.0 and not @value.nan? end def to_ruby to_f end def to_predicate to_f end def to_number(context) self end def ==(other) if other.is_a? XPathNodeSet or other.is_a? XPathBoolean then other == self else @value == other.to_f end end def +(other) @value += other.to_f self end def -(other) @value -= other.to_f self end def *(other) @value *= other.to_f self end def /(other) @value /= other.to_f self end def %(other) n = other.to_f f = @value % n f = -f if @value < 0 f = -f if n < 0 @value = f self end def -@ @value = -@value self end def floor @value = @value.floor.to_f self end def ceil @value = @value.ceil.to_f self end def round f = @value unless f.nan? or f.infinite? then if f >= 0.0 then @value = f.round.to_f elsif f - f.truncate >= -0.5 then @value = f.ceil.to_f else @value = f.floor.to_f end end self end end class XPathString include XPathObject def initialize(str) raise ::TypeError, "must be a String" unless str.is_a? String @value = str end def to_str @value end def to_f if /\A\s*(-?\d+\.?\d*)(?:\s|\z)/ =~ @value then $1.to_f else 0.0 / 0.0 # NaN end end def true? not @value.empty? end def to_ruby to_str end def to_string(context) self end def concat(s) @value = @value + s self end def start_with?(s) /\A#{Regexp.quote(s)}/ =~ @value end def contain?(s) /#{Regexp.quote(s)}/ =~ @value end def substring_before(s) if /#{Regexp.quote(s)}/ =~ @value then @value = $` else @value = '' end self end def substring_after(s) if /#{Regexp.quote(s)}/ =~ @value then @value = $' else @value = '' end self end def substring(start, len) start = start.round.to_f if start.infinite? or start.nan? then @value = '' elsif len then len = len.round.to_f maxlen = start + len len = maxlen - 1.0 if len >= maxlen if start <= 1.0 then start = 0 else start = start.to_i - 1 end if len.nan? or len < 1.0 then @value = '' elsif len.infinite? then # @value = @value[start..-1] /\A[\W\w]{0,#{start}}/ =~ @value @value = $' else # @value = @value[start, len.to_i] /\A[\W\w]{0,#{start}}([\W\w]{0,#{len.to_i}})/ =~ @value @value = $1 end elsif start > 1.0 then # @value = @value[(start-1)..-1] /\A[\W\w]{0,#{start.to_i-1}}/ =~ @value @value = $' end raise "BUG" unless @value self end def size @value.gsub(/[^\Wa-zA-Z_\d]/, ' ').size end def normalize_space @value = @value.strip @value.gsub!(/\s+/, ' ') self end def translate(from, to) to = to.split(//) h = {} from.split(//).each_with_index { |i,n| h[i] = to[n] unless h.key? i } @value = @value.gsub(/[#{Regexp.quote(h.keys.join)}]/) { |s| h[s] } self end def replace(str) @value = str self end end class Compiler < Racc::Parser module_eval <<'..end xpath.ry modeval..idcc62899492', 'xpath.ry', 268 module CompilePhaseObject def invoke_conv(expr, conv_method) return unless conv_method if conv_method == '.to_number' or conv_method == '.to_string' or conv_method == '.to_boolean' then expr.push conv_method, '(', nil, ')' else expr.push conv_method end end private :invoke_conv end module ConstantObject include CompilePhaseObject def to_string StringConstant.new to_str end def to_number NumberConstant.new self end def to_boolean if true? then ConstantTrue else ConstantFalse end end end module BooleanConstant include ConstantObject def value_type :boolean end def expr(conv_method = nil) if conv_method == '.to_ruby' or conv_method == '.true?' then [ true?.to_s ] else ret = [ nil, '.make_boolean(', true?.to_s, ')' ] invoke_conv ret, conv_method unless conv_method == '.to_boolean' ret end end end class ConstantTrueClass < XPathTrueClass include BooleanConstant @instance = new end class ConstantFalseClass < XPathFalseClass include BooleanConstant @instance = new end ConstantTrue = ConstantTrueClass.instance ConstantFalse = ConstantFalseClass.instance class NumberConstant < XPathNumber include ConstantObject def value_type :number end def initialize(src) f = src.to_f if src.is_a? ConstantObject and s = dump_float(f) then src = s end @src = [ src ] @precedence = 1 super f end attr_reader :precedence protected :precedence def to_number self end def expr(conv_method = nil) @src.collect! { |i| if i.is_a? ConstantObject then i.expr '.to_f' else i end } expr = @src expr.flatten! @src = :draff # for debug unless conv_method == '.to_ruby' or conv_method == '.to_f' then expr[0, 0] = [ nil, '.make_number(' ] expr.push(')') invoke_conv expr, conv_method unless conv_method == '.to_number' end expr end private def dump_float(f) if f.finite? and f == eval(s = f.to_s) then s elsif f.infinite? then if f > 0 then '(1.0 / 0.0)' else '(-1.0 / 0.0)' end elsif f.nan? then '(0.0 / 0.0)' else nil end end def concat(op, other, prec) @src.unshift('(').push(')') if @precedence < prec if other.precedence < prec then @src.push(op).push('(').concat(other.expr('.to_f')).push(')') else @src.push(op).concat(other.expr('.to_f')) end @precedence = prec end public def self.def_arithmetic_operator(op, precedence) module_eval <<_, __FILE__, __LINE__ + 1 def #{op}(other) super other if s = dump_float(@value) then @src.clear @src.push s else concat ' #{op} ', other, #{precedence} end self end _ end def_arithmetic_operator '+', 0 def_arithmetic_operator '-', 0 def_arithmetic_operator '*', 1 def_arithmetic_operator '/', 1 class << self undef def_arithmetic_operator end def %(other) orig = @value super other if s = dump_float(@value) then @src.clear @src.push s else f = other.to_f other = -other if orig % f == -@value concat ' % ', other, 1 end self end def -@ super if s = dump_float(@value) then @src.clear @src.push s else if @src.size == 1 then @src.unshift '-' else @src.unshift('-(').push(')') end @precedence = 1 end self end end class StringConstant < XPathString include ConstantObject def value_type :string end def to_string self end def expr(conv_method = nil) if conv_method == '.to_ruby' or conv_method == '.to_str' then [ @value.dump ] else ret = [ nil, '.make_string(', @value.dump, ')' ] invoke_conv ret, conv_method unless conv_method == '.to_string' ret end end end class Expression include CompilePhaseObject def initialize(expr) if expr.is_a? ConstantObject then @value = expr else raise "BUG" unless expr.is_a? Array @value = nil @valuetype = nil @expr = expr end @unary = true end attr_reader :value def value_type if @value then @value.value_type else @valuetype end end def unarize unless @unary then @expr.unshift('(').push(')') @unary = true end self end def self.def_comparison_operator(name, op) module_eval <<_, __FILE__, __LINE__ + 1 def #{name}(other) if @value and other.value then if @value #{op} other.value then @value = ConstantTrue else @value = ConstantFalse end @unary = true else @expr = expr.push(' #{op} ').concat(other.expr) @valuetype = :ruby_boolean @unary = false end self end _ end def self.def_arithmetic_operator(*ops) ops.each { |op| module_eval <<_, __FILE__, __LINE__ + 1 def #{op}(other) if @value and other.value then @value = @value.to_number #{op} other.value.to_number else @expr = expr('.to_number').push(' #{op} ') # not 'to_number', for a little speed up :-) @expr.concat other.expr('.to_f') @valuetype = :number @unary = false end self end _ } end def_comparison_operator 'eq', '==' def_comparison_operator 'neq', '!=' def_comparison_operator 'lt', '<' def_comparison_operator 'gt', '>' def_comparison_operator 'le', '<=' def_comparison_operator 'ge', '>=' def_arithmetic_operator '+', '-', '*', '/', '%' class << self undef def_comparison_operator undef def_arithmetic_operator end def -@ if @value then @value = -@value.to_number else unarize @expr = expr('.to_number').unshift('-') end self end def logical_or(other) if @value and @value.true? then @value = ConstantTrue @unary = true @expr = @valuetype = nil else @expr = expr('.true?').push(' || ').concat(other.expr('.true?')) @valuetype = :ruby_boolean @unary = false end self end def logical_and(other) if @value and not @value.true? then @value = ConstantFalse @unary = true @expr = @valuetype = nil else @expr = expr('.true?').push(' && ').concat(other.expr('.true?')) @valuetype = :ruby_boolean @unary = false end self end def **(other) @expr = expr.push(' ** ').concat(other.expr) @valuetype = nil @unary = false self end def add_predicate(pred) unarize @expr = expr.concat(pred) @valuetype = nil self end def <<(other) path = other.expr path.shift # nil path.shift # .to_nodeset add_predicate path end def add_step(axis) add_predicate [ ".step(:#{axis.tr('-','_')})" ] end def expr(conv_method = nil) if @value then ret = @value.expr(conv_method) @value = nil elsif @valuetype == :ruby_boolean then ret = @expr unless conv_method == '.to_ruby' or conv_method == '.true?' then ret[0, 0] = [ nil, '.make_boolean(' ] ret.push ')' invoke_conv ret, conv_method unless conv_method == '.to_boolean' end elsif @valuetype == :number and conv_method == '.to_number' then ret = @expr elsif @valuetype == :string and conv_method == '.to_string' then ret = @expr elsif @valuetype == :boolean and conv_method == '.to_boolean' then ret = @expr else if conv_method then unarize invoke_conv @expr, conv_method end ret = @expr end @expr = :draff # for debug ret end end class LocationPath include CompilePhaseObject def initialize @root = false @steps = [] # [ axis, [ tests ], predicates ] end attr_reader :root, :steps protected :root, :steps def absolute! @root = true self end def add_step(axis, nodetype = false, localpart = false, namespace = false, predicate = nil) if nodetype == false and localpart == false and namespace == false then append_step axis, [], predicate else append_step axis, [ [ nodetype, localpart, namespace ] ], predicate end self end def <<(other) raise "BUG" if other.root other = other.steps other.each { |step| if step[0] then append_step(*step) else add_predicate(step[2]) end } self end def add_predicate(pred) @steps.push [ nil, nil, pred ] self end def **(other) unless other.is_a? LocationPath then ret = nil else othersteps = other.steps size = @steps.size unless size == othersteps.size then othersize = othersteps.size if size >= othersize then ret = (@steps[0, othersize] == othersize and self) else ret = (othersteps[0, size] == @steps and other) end else last = @steps.pop otherlast = othersteps.pop if @steps == othersteps and mix_step(last, otherlast) then ret = self else ret = nil end @steps.push last othersteps.push otherlast end end ret or Expression.new(expr) ** other end private UnifiableAxes = { 'descendant' => { 'descendant-or-self' => 'descendant', }, 'descendant-or-self' => { 'child' => 'descendant', 'descendant' => 'descendant', 'descendant-or-self' => 'descendant-or-self', }, 'ancestor' => { 'ancestor-or-self' => 'ancestor', }, 'ancestor-or-self' => { 'parent' => 'ancestor', 'ancestor' => 'ancestor', 'ancestor-or-self' => 'ancestor-or-self', }, 'following-sibling' => { 'following-sibling' => 'following-sibling', }, 'preceding-sibling' => { 'preceding-sibling' => 'preceding-sibling', }, 'following' => { 'following' => 'following', 'following-sibling' => 'following', }, 'preceding' => { 'preceding' => 'preceding', 'preceding-sibling' => 'preceding', }, 'child' => { 'following-sibling' => 'child', 'preceding-sibling' => 'child', }, } UnifiableAxes.default = {} def append_step(axis, test, predicate) lastaxis, lasttest, lastpred = laststep = @steps.last if axis == 'self' and test.empty? then @steps.push [ nil, nil, predicate ] if predicate elsif lastaxis and lasttest.empty? and not lastpred and not predicate and w = UnifiableAxes[lastaxis][axis] then laststep[0] = w laststep[1] = test else @steps.push [ axis, test, predicate ] end end def mix_step(step, other) if step[0] and step[0] == other[0] and step[2] == other[2] then step[1].concat other[1] step else nil end end public def expr(conv_method = nil) if @root then expr = [ nil, '.root_nodeset' ] else expr = [ nil, '.to_nodeset' ] end @steps.each { |axis,test,predicate| if axis.nil? then # predicate only expr.concat predicate elsif test.empty? and not predicate then expr.push ".select_all(:#{axis.tr('-','_')})" else expr.push ".step(:#{axis.tr('-','_')})" if test.empty? then expr.push ' { |n| n.select_all' else expr.push ' { |n| n.select { |i| ' test.each { |nodetype,localpart,namespace| if nodetype then expr.push "i.node_type == :#{nodetype.tr('-','_')}", ' && ' end if localpart then expr.push "i.name_localpart == #{localpart.dump}", ' && ' end if namespace.nil? then expr.push 'i.namespace_uri.nil?', ' && ' elsif namespace then namespace = namespace.dump expr.push('i.namespace_uri == ', nil, ".get_namespace(#{namespace})", ' && ') end expr[-1] = ' or ' } expr[-1] = ' }' end expr.concat predicate if predicate expr.push ' }' end } @steps = :draff # for debug invoke_conv expr, conv_method expr end def value_type nil end def value nil end def unarize self end def self.redirect_to_expr(*ops) ops.each { |op| name = op name = op[1..-1] if op[0] == ?. module_eval <<_, __FILE__, __LINE__ + 1 def #{name}(arg) ; Expression.new(expr) #{op} arg ; end _ } end redirect_to_expr('.eq', '.neq', '.lt', '.gt', '.le', '.ge', '+', '-', '*', '/', '%', '.logical_or', '.logical_and') class << self undef redirect_to_expr end def -@ -Expression.new(expr) end end Delim = '\\s\\(\\)\\[\\]\\.@,\\/\\|\\*\\+"\'=!<>:' Name = "[^-#{Delim}][^#{Delim}]*" Operator = { '@' => true, '::' => true, '(' => true, '[' => true, :MUL => true, 'and' => true, 'or' => true, 'mod' => true, 'div' => true, '/' => true, '//' => true, '|' => true, '+' => true, '-' => true, '=' => true, '!=' => true, '<' => true, '<=' => true, '>' => true, '>=' => true, ':' => false # ':' '*' => '*' must not be a MultiplyOperator # ':' 'and' => 'and' must be a OperatorName } NodeType = { 'comment' => true, 'text' => true, 'processing-instruction' => true, 'node' => true, } private def axis?(s) /\A[-a-zA-Z]+\z/ =~ s end def nodetype?(s) NodeType.key? s end def tokenize(src) token = [] src.scan(/(\.\.?|\/\/?|::?|!=|[<>]=?|[-()\[\].@,|+=*])| ("[^"]*"|'[^']*')|(\d+\.?\d*)| (\$?#{Name}(?::#{Name})?)| \s+|./ox) { |delim,literal,number,name| #/ if delim then if delim == '*' then delim = :MUL if (prev = token[-1]) and not Operator.key? prev[0] elsif delim == '::' then prev = token[-1] if prev and prev[0] == :Name and axis? prev[1] then prev[0] = :AxisName end elsif delim == '(' then if (prev = token[-1]) and prev[0] == :Name then if nodetype? prev[1] then prev[0] = :NodeType else prev[0] = :FuncName end end end token.push [ delim, delim ] elsif name then prev = token[-1] if name[0] == ?$ then name[0,1] = '' token.push [ :Variable, name ] elsif Operator.key? name and (prev = token[-1]) and not Operator[prev[0]] then token.push [ name, name ] else token.push [ :Name, name ] end elsif number then number << '.0' unless number.include? ?. token.push [ :Number, number ] elsif literal then literal.chop! literal[0,1] = '' token.push [ :Literal, literal ] else s = $&.strip token.push [ s, s ] unless s.empty? end } token end public def compile(src, pattern = false) @token = tokenize(src) @token.push [ false, :end ] @token.each { |i| p i } if @yydebug @token.reverse! @token.push [ :PATTERN, nil ] if pattern @context = 'context0' ret = do_parse ret = ret.unshift("proc { |context0| ").push(" }").join print ">>>>\n", ret, "\n<<<<\n" if @yydebug XPathProc.new eval(ret), src end def initialize(debug = false) super() @yydebug = debug end private def next_token @token.pop end def is_xpointer? false end def on_error(*args) # tok, val, values raise CompileError, 'parse error' end ..end xpath.ry modeval..idcc62899492 ##### racc 1.3.3 generates ### racc_reduce_table = [ 0, 0, :racc_error, 0, 39, :_reduce_1, 1, 39, :_reduce_2, 2, 39, :_reduce_3, 1, 41, :_reduce_none, 3, 41, :_reduce_5, 3, 40, :_reduce_6, 3, 40, :_reduce_7, 3, 40, :_reduce_8, 3, 40, :_reduce_9, 3, 40, :_reduce_10, 3, 40, :_reduce_11, 3, 40, :_reduce_12, 3, 40, :_reduce_13, 3, 40, :_reduce_14, 3, 40, :_reduce_15, 2, 40, :_reduce_16, 3, 40, :_reduce_17, 3, 40, :_reduce_18, 3, 40, :_reduce_19, 3, 40, :_reduce_20, 1, 40, :_reduce_none, 1, 40, :_reduce_none, 3, 40, :_reduce_23, 3, 40, :_reduce_24, 1, 43, :_reduce_25, 3, 43, :_reduce_26, 1, 43, :_reduce_27, 1, 43, :_reduce_28, 1, 43, :_reduce_29, 2, 43, :_reduce_30, 4, 45, :_reduce_31, 0, 47, :_reduce_32, 1, 47, :_reduce_33, 3, 47, :_reduce_34, 0, 48, :_reduce_35, 0, 49, :_reduce_36, 5, 46, :_reduce_37, 1, 42, :_reduce_38, 2, 42, :_reduce_39, 2, 42, :_reduce_40, 1, 42, :_reduce_none, 1, 44, :_reduce_42, 3, 44, :_reduce_43, 3, 44, :_reduce_44, 0, 51, :_reduce_45, 0, 52, :_reduce_46, 8, 44, :_reduce_47, 1, 50, :_reduce_48, 1, 50, :_reduce_49, 3, 50, :_reduce_50, 0, 55, :_reduce_none, 2, 55, :_reduce_52, 1, 54, :_reduce_53, 1, 54, :_reduce_54, 3, 54, :_reduce_55, 4, 54, :_reduce_56, 0, 56, :_reduce_none, 1, 56, :_reduce_none, 0, 53, :_reduce_59, 1, 53, :_reduce_60, 2, 53, :_reduce_none ] racc_reduce_n = 62 racc_shift_n = 100 racc_action_table = [ -1, 75, 87, 16, 19, 16, 19, 82, 2, 9, 12, 9, 12, 42, 44, 47, 48, 5, 7, 10, 14, 18, 43, 20, 1, 4, 71, 2, 84, 16, 19, 26, 83, 47, 48, 9, 12, 7, 10, 14, 18, 26, 20, 1, 4, 46, 2, 26, 16, 19, 52, 49, 54, 50, 9, 12, 7, 10, 14, 18, -32, 20, 1, 4, -32, 2, 88, 16, 19, 47, 48, 47, 48, 9, 12, 7, 10, 14, 18, -32, 20, 1, 4, -32, 2, 90, 16, 19, 47, 48, 43, 79, 9, 12, 7, 10, 14, 18, 78, 20, 1, 4, 95, 2, 26, 16, 19, 96, 37, 83, 99, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, nil, 2, nil, 16, 19, nil, nil, nil, nil, 9, 12, 7, 10, 14, 18, nil, 20, 1, 4, 16, 19, nil, 16, 19, nil, 9, 12, nil, 9, 12, 26, nil, 29, 31, 33, 35, 23, 25, 27, 28, 30, 32, 34, 36, 24, 7, 10, 7, 10, 26, 81, 29, 31, 33, 16, 19, 16, 19, 16, 19, 9, 12, 9, 12, 9, 12, 26, nil, 29, 31, 33, 35, 23, 25, 27, 28, 30, 32, 34, 36, 24, 26, nil, 29, 31, 33, 35, 23, 25, 27, 28, 30, 32, 34, 36, 24, 26, nil, 29, 31, 33, 35, 23, 25, 27, 28, 30, 32, 34, 36, 24, 26, nil, 29, 31, 33, 35, 23, 25, 27, 28, 30, 32, 34, 36, 24, 26, nil, 29, 31, 33, 35, 23, 25, 27, 28, 30, 32, 34, 36, 26, nil, 29, 31, 33, 35, 23, 25, 27, 28, 30, 32, 34, 26, nil, 29, 31, 33, 35, 23, 25, 27, 28, 30, 26, nil, 29, 31, 33, 35, 23, 25, 27, 28, 30, 16, 19, -59, -59, nil, -59, 9, 12, 16, 19, nil, nil, nil, nil, 9, 12, 26, nil, 29, 31, 33, 35, 23, 26, nil, 29, 31, 33, 35, 23, 26, nil, 29, 31, 33, 35, 23, 26, nil, 29, 31, 33, 35, 23, 26, nil, 29, 31, 33 ] racc_action_check = [ 0, 47, 75, 48, 48, 47, 47, 54, 0, 48, 48, 47, 47, 11, 11, 40, 40, 0, 0, 0, 0, 0, 11, 0, 0, 0, 38, 23, 70, 0, 0, 61, 70, 13, 13, 0, 0, 23, 23, 23, 23, 22, 23, 23, 23, 12, 37, 63, 23, 23, 15, 15, 21, 15, 23, 23, 37, 37, 37, 37, 37, 37, 37, 37, 37, 94, 78, 37, 37, 41, 41, 74, 74, 37, 37, 94, 94, 94, 94, 94, 94, 94, 94, 94, 36, 79, 94, 94, 72, 72, 80, 50, 94, 94, 36, 36, 36, 36, 49, 36, 36, 36, 89, 35, 65, 36, 36, 93, 4, 97, 98, 36, 36, 35, 35, 35, 35, nil, 35, 35, 35, nil, 34, nil, 35, 35, nil, nil, nil, nil, 35, 35, 34, 34, 34, 34, nil, 34, 34, 34, nil, 83, nil, 34, 34, nil, nil, nil, nil, 34, 34, 83, 83, 83, 83, nil, 83, 83, 83, nil, 73, nil, 83, 83, nil, nil, nil, nil, 83, 83, 73, 73, 73, 73, nil, 73, 73, 73, nil, 33, nil, 73, 73, nil, nil, nil, nil, 73, 73, 33, 33, 33, 33, nil, 33, 33, 33, nil, 32, nil, 33, 33, nil, nil, nil, nil, 33, 33, 32, 32, 32, 32, nil, 32, 32, 32, nil, 18, nil, 32, 32, nil, nil, nil, nil, 32, 32, 18, 18, 18, 18, nil, 18, 18, 18, nil, 31, nil, 18, 18, nil, nil, nil, nil, 18, 18, 31, 31, 31, 31, nil, 31, 31, 31, nil, 30, nil, 31, 31, nil, nil, nil, nil, 31, 31, 30, 30, 30, 30, nil, 30, 30, 30, nil, 2, nil, 30, 30, nil, nil, nil, nil, 30, 30, 2, 2, 2, 2, nil, 2, 2, 2, nil, 29, nil, 2, 2, nil, nil, nil, nil, 2, 2, 29, 29, 29, 29, nil, 29, 29, 29, nil, 25, nil, 29, 29, nil, nil, nil, nil, 29, 29, 25, 25, 25, 25, nil, 25, 25, 25, nil, 26, nil, 25, 25, nil, nil, nil, nil, 25, 25, 26, 26, 26, 26, nil, 26, 26, 26, nil, 27, nil, 26, 26, nil, nil, nil, nil, 26, 26, 27, 27, 27, 27, nil, 27, 27, 27, nil, 28, nil, 27, 27, nil, nil, nil, nil, 27, 27, 28, 28, 28, 28, nil, 28, 28, 28, nil, 24, nil, 28, 28, nil, nil, nil, nil, 28, 28, 24, 24, 24, 24, nil, 24, 24, 24, 44, 44, nil, 24, 24, nil, 44, 44, nil, 24, 24, 53, nil, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 71, 71, 5, 5, 67, 53, 67, 67, 67, 42, 42, 71, 71, 5, 5, 42, 42, 71, 71, 5, 5, 3, nil, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 69, nil, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 92, nil, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 86, nil, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 56, nil, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 68, nil, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 66, nil, 66, 66, 66, 66, 66, 66, 66, 66, 66, 64, nil, 64, 64, 64, 64, 64, 64, 64, 64, 64, 7, 7, 7, 7, nil, 7, 7, 7, 10, 10, nil, nil, nil, nil, 10, 10, 57, nil, 57, 57, 57, 57, 57, 60, nil, 60, 60, 60, 60, 60, 59, nil, 59, 59, 59, 59, 59, 62, nil, 62, 62, 62, 62, 62, 55, nil, 55, 55, 55 ] racc_action_pointer = [ 0, nil, 266, 451, 87, 416, nil, 533, nil, nil, 541, -5, 8, 15, nil, 19, nil, nil, 209, nil, nil, 52, 39, 19, 380, 304, 323, 342, 361, 285, 247, 228, 190, 171, 114, 95, 76, 38, 24, nil, -3, 51, 412, nil, 377, nil, nil, -24, -26, 65, 70, nil, nil, 415, 7, 604, 511, 576, nil, 590, 583, 29, 597, 45, 549, 102, 538, 434, 525, 466, 6, 414, 70, 152, 53, -19, nil, nil, 35, 62, 63, nil, nil, 133, nil, nil, 496, nil, nil, 80, nil, nil, 481, 79, 57, nil, nil, 83, 88, nil ] racc_action_default = [ -59, -28, -59, -2, -62, -59, -42, -38, -21, -60, -59, -22, -62, -41, -25, -62, -48, -29, -59, -49, -27, -62, -16, -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, -59, -3, -4, -39, -40, -59, -35, -59, -30, -61, -59, -59, -54, -62, -51, -53, -62, -62, -15, -6, -10, -20, -11, -12, -17, -13, -18, -8, -19, -9, -14, -7, -33, -62, -59, -23, -59, -24, -62, -43, -44, -62, -57, -50, -26, 100, -59, -31, -5, -36, -45, -55, -62, -58, -52, -34, -62, -59, -56, -37, -46, -62, -47 ] racc_goto_table = [ 3, 45, 22, 39, 70, 40, 76, 77, 41, 73, 38, 93, 21, 94, 98, 51, 80, 89, 53, nil, nil, nil, nil, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, nil, nil, nil, 72, nil, 74, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 97, nil, nil, nil, nil, nil, nil, nil, 85, 91, nil, nil, 86, nil, nil, nil, nil, nil, nil, nil, nil, nil, 92 ] racc_goto_check = [ 2, 8, 2, 4, 9, 6, 12, 12, 6, 10, 3, 11, 1, 13, 14, 16, 17, 18, 2, nil, nil, nil, nil, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, nil, nil, nil, 6, nil, 6, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 9, nil, nil, nil, nil, nil, nil, nil, 4, 8, nil, nil, 2, nil, nil, nil, nil, nil, nil, nil, nil, nil, 2 ] racc_goto_pointer = [ nil, 12, 0, 5, -2, nil, -2, nil, -10, -33, -34, -75, -41, -74, -83, nil, 0, -35, -62 ] racc_goto_default = [ nil, nil, 69, nil, 8, 11, 13, 17, nil, nil, nil, nil, 6, nil, nil, 15, nil, nil, nil ] racc_token_table = { false => 0, Object.new => 1, "|" => 2, :NEG => 3, :MUL => 4, "div" => 5, "mod" => 6, "+" => 7, "-" => 8, "<" => 9, ">" => 10, "<=" => 11, ">=" => 12, "=" => 13, "!=" => 14, "and" => 15, "or" => 16, :PATTERN => 17, "/" => 18, "//" => 19, :Variable => 20, "(" => 21, ")" => 22, :Literal => 23, :Number => 24, :FuncName => 25, "," => 26, "[" => 27, "]" => 28, "." => 29, ".." => 30, "*" => 31, :Name => 32, ":" => 33, :NodeType => 34, "@" => 35, :AxisName => 36, "::" => 37 } racc_use_result_var = false racc_nt_base = 38 Racc_arg = [ racc_action_table, racc_action_check, racc_action_default, racc_action_pointer, racc_goto_table, racc_goto_check, racc_goto_default, racc_goto_pointer, racc_nt_base, racc_reduce_table, racc_token_table, racc_shift_n, racc_reduce_n, racc_use_result_var ] Racc_debug_parser = false ##### racc system variables end ##### # reduce 0 omitted module_eval <<'.,.,', 'xpath.ry', 27 def _reduce_1( val, _values) [] end .,., module_eval <<'.,.,', 'xpath.ry', 30 def _reduce_2( val, _values) expr = val[0].expr('.to_ruby') expr.collect! { |i| i or @context } expr end .,., module_eval <<'.,.,', 'xpath.ry', 36 def _reduce_3( val, _values) expr = val[0].expr('.to_ruby') expr.collect! { |i| i or @context } expr end .,., # reduce 4 omitted module_eval <<'.,.,', 'xpath.ry', 43 def _reduce_5( val, _values) val[0] ** val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 46 def _reduce_6( val, _values) val[0].logical_or val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 48 def _reduce_7( val, _values) val[0].logical_and val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 50 def _reduce_8( val, _values) val[0].eq val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 52 def _reduce_9( val, _values) val[0].neq val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 54 def _reduce_10( val, _values) val[0].lt val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 56 def _reduce_11( val, _values) val[0].gt val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 58 def _reduce_12( val, _values) val[0].le val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 60 def _reduce_13( val, _values) val[0].ge val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 62 def _reduce_14( val, _values) val[0] + val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 64 def _reduce_15( val, _values) val[0] - val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 66 def _reduce_16( val, _values) -val[1] end .,., module_eval <<'.,.,', 'xpath.ry', 68 def _reduce_17( val, _values) val[0] * val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 70 def _reduce_18( val, _values) val[0] / val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 72 def _reduce_19( val, _values) val[0] % val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 75 def _reduce_20( val, _values) # Why `**' is used for unionizing node-sets is that its # precedence is higher than any other binary operators # in Ruby. val[0] ** val[2] end .,., # reduce 21 omitted # reduce 22 omitted module_eval <<'.,.,', 'xpath.ry', 83 def _reduce_23( val, _values) val[0] << val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 85 def _reduce_24( val, _values) val[0].add_step('descendant-or-self') << val[2] end .,., module_eval <<'.,.,', 'xpath.ry', 89 def _reduce_25( val, _values) Expression.new [ nil,'.get_variable(',val[0].dump,')' ] end .,., module_eval <<'.,.,', 'xpath.ry', 92 def _reduce_26( val, _values) val[1].unarize end .,., module_eval <<'.,.,', 'xpath.ry', 94 def _reduce_27( val, _values) Expression.new StringConstant.new(val[0]) end .,., module_eval <<'.,.,', 'xpath.ry', 96 def _reduce_28( val, _values) Expression.new NumberConstant.new(val[0]) end .,., module_eval <<'.,.,', 'xpath.ry', 98 def _reduce_29( val, _values) Expression.new val[0] end .,., module_eval <<'.,.,', 'xpath.ry', 100 def _reduce_30( val, _values) val[0].add_predicate val[1] end .,., module_eval <<'.,.,', 'xpath.ry', 104 def _reduce_31( val, _values) val[2][0,0] = [ nil, ".funcall(#{val[0].dump}" ] val[2].push(')') end .,., module_eval <<'.,.,', 'xpath.ry', 109 def _reduce_32( val, _values) [] end .,., module_eval <<'.,.,', 'xpath.ry', 111 def _reduce_33( val, _values) val[0].expr.unshift ', ' end .,., module_eval <<'.,.,', 'xpath.ry', 113 def _reduce_34( val, _values) val[0].push(', ').concat(val[2].expr) end .,., module_eval <<'.,.,', 'xpath.ry', 117 def _reduce_35( val, _values) c = @context @context = c.succ c end .,., module_eval <<'.,.,', 'xpath.ry', 123 def _reduce_36( val, _values) c = @context @context = _values[-2] c end .,., module_eval <<'.,.,', 'xpath.ry', 129 def _reduce_37( val, _values) expr = val[2] valuetype = expr.value_type value = expr.value if valuetype == :number then if value then f = value.to_f if f > 0 and f.truncate == f then [ ".at(#{f.to_i})" ] else [ '.at(0)' ] # clear end else expr.expr('.to_f'). unshift('.at(').push(')') end elsif value then if value.true? then [] else [ '.at(0)' ] # clear end else c = val[3] if valuetype == :ruby_boolean then conv = '.true?' else conv = '.to_predicate' end a = expr.expr(conv) a.collect! { |i| i or c } a.unshift(".predicate { |#{c}| ").push(' }') end end .,., module_eval <<'.,.,', 'xpath.ry', 164 def _reduce_38( val, _values) LocationPath.new.absolute! end .,., module_eval <<'.,.,', 'xpath.ry', 166 def _reduce_39( val, _values) val[1].absolute! end .,., module_eval <<'.,.,', 'xpath.ry', 169 def _reduce_40( val, _values) path = LocationPath.new path.absolute! path.add_step('descendant-or-self') << val[1] end .,., # reduce 41 omitted module_eval <<'.,.,', 'xpath.ry', 176 def _reduce_42( val, _values) LocationPath.new.add_step(*val[0]) end .,., module_eval <<'.,.,', 'xpath.ry', 178 def _reduce_43( val, _values) val[0].add_step(*val[2]) end .,., module_eval <<'.,.,', 'xpath.ry', 181 def _reduce_44( val, _values) val[0].add_step('descendant-or-self').add_step(*val[2]) end .,., module_eval <<'.,.,', 'xpath.ry', 186 def _reduce_45( val, _values) c = @context @context = c.succ c end .,., module_eval <<'.,.,', 'xpath.ry', 192 def _reduce_46( val, _values) c = @context @context = _values[-2] c end .,., module_eval <<'.,.,', 'xpath.ry', 198 def _reduce_47( val, _values) on_error unless is_xpointer? args = val[5] c = val[6] args.collect! { |i| i or c } args[0] = ".funcall(#{val[2].dump}) { |#{c}| [" args.push '] }' val[0].add_predicate args end .,., module_eval <<'.,.,', 'xpath.ry', 208 def _reduce_48( val, _values) [ 'self', false, false, false, nil ] end .,., module_eval <<'.,.,', 'xpath.ry', 210 def _reduce_49( val, _values) [ 'parent', false, false, false, nil ] end .,., module_eval <<'.,.,', 'xpath.ry', 213 def _reduce_50( val, _values) nodetest = val[1] unless nodetest[0] then axis = val[0] if axis != 'attribute' and axis != 'namespace' then nodetest[0] = 'element' end end nodetest[0] = false if nodetest[0] == 'node' nodetest.unshift(val[0]).push(val[2]) end .,., # reduce 51 omitted module_eval <<'.,.,', 'xpath.ry', 226 def _reduce_52( val, _values) (val[0] || []).concat val[1] end .,., module_eval <<'.,.,', 'xpath.ry', 229 def _reduce_53( val, _values) [ false, false, false ] end .,., module_eval <<'.,.,', 'xpath.ry', 232 def _reduce_54( val, _values) if /:/ =~ val[0] then [ false, $', $` ] #' <= for racc else [ false, val[0], nil ] end end .,., module_eval <<'.,.,', 'xpath.ry', 240 def _reduce_55( val, _values) on_error if /:/ =~ val[0] [ false, false, val[0] ] end .,., module_eval <<'.,.,', 'xpath.ry', 245 def _reduce_56( val, _values) nodetype = val[0] arg = val[2] if arg and nodetype != 'processing-instruction' then raise CompileError, "nodetest #{nodetype}() requires no argument" end [ nodetype, arg || false, false ] end .,., # reduce 57 omitted # reduce 58 omitted module_eval <<'.,.,', 'xpath.ry', 258 def _reduce_59( val, _values) 'child' end .,., module_eval <<'.,.,', 'xpath.ry', 260 def _reduce_60( val, _values) 'attribute' end .,., # reduce 61 omitted def _reduce_none( val, _values) val[0] end end # class Compiler # # Client NodeVisitor a NodeAdapter a Node # | | | | # |=| | | | # | |--{visit(node)}-->|=| | | # | | | |---{accept(self)}----------------->|=| # | | |=| | | | # | | | | | | # | | |=|<------------------{on_**(self)}---|=| # | | | | | | # | | | |--{wrap(node)}-->|=| | # | | | | | | | # | | | | |=| | # | |<--[NodeAdapter]--|=| | | # | | | | | # | |-----{request}----------------------->|=| | # | | | | |--{request}--->|=| # | | | | | | | # | | | | |<-----[Data]---|=| # | |<--------------------------[Data]-----|=| | # | | | | | # |=| | | | # | | | | # class TransparentNodeVisitor def visit(node) node end end class NullNodeAdapter def node self end def root nil end def parent nil end def children [] end def each_following_siblings end def each_preceding_siblings end def attributes [] end def namespaces [] end def index 0 end def node_type nil end def name_localpart nil end def qualified_name name_localpart end def namespace_uri nil end def string_value '' end def lang nil end def select_id(*ids) raise XPath::Error, "selection by ID is not supported" end end class AxisIterator def reverse_order? false end end class ReverseAxisIterator < AxisIterator def reverse_order? true end end class SelfIterator < AxisIterator def each(node, visitor) yield visitor.visit(node) end end class ChildIterator < AxisIterator def each(node, visitor, &block) visitor.visit(node).children.each { |i| yield visitor.visit(i) } end end class ParentIterator < AxisIterator def each(node, visitor) parent = visitor.visit(node).parent yield visitor.visit(parent) if parent end end class AncestorIterator < ReverseAxisIterator def each(node, visitor) node = visitor.visit(node).parent while node i = visitor.visit(node) parent = i.parent yield i node = parent end end end class AncestorOrSelfIterator < AncestorIterator def each(node, visitor) yield visitor.visit(node) super end end class DescendantIterator < AxisIterator def each(node, visitor) stack = visitor.visit(node).children.reverse while node = stack.pop i = visitor.visit(node) stack.concat i.children.reverse yield i end end end class DescendantOrSelfIterator < DescendantIterator def each(node, visitor) yield visitor.visit(node) super end end class FollowingSiblingIterator < AxisIterator def each(node, visitor) visitor.visit(node).each_following_siblings { |i| yield visitor.visit(i) } end end class PrecedingSiblingIterator < ReverseAxisIterator def each(node, visitor) visitor.visit(node).each_preceding_siblings { |i| yield visitor.visit(i) } end end class FollowingIterator < DescendantOrSelfIterator def each(node, visitor) while parent = (a = visitor.visit(node)).parent a.each_following_siblings { |i| super i, visitor } node = parent end end end class PrecedingIterator < ReverseAxisIterator def each(node, visitor) while parent = (adaptor = visitor.visit(node)).parent adaptor.each_preceding_siblings { |i| stack = visitor.visit(i).children.dup while node = stack.pop a = visitor.visit(node) stack.concat a.children yield a end yield visitor.visit(i) } node = parent end end end class AttributeIterator < AxisIterator def each(node, visitor) visitor.visit(node).attributes.each { |i| yield visitor.visit(i) } end end class NamespaceIterator < AxisIterator def each(node, visitor) visitor.visit(node).namespaces.each { |i| yield visitor.visit(i) } end end class XPathNodeSet class LocationStep < XPathNodeSet def initialize(context) @context = context @visitor = context.visitor @nodes = [] end def set_iterator(iterator) @iterator = iterator end def reuse(node) @node = node @nodes.clear end def select @iterator.each(@node, @visitor) { |i| node = i.node @nodes.push node if yield(i) } self end def select_all @iterator.each(@node, @visitor) { |i| @nodes.push i.node } self end end include XPathObject def initialize(context, *nodes) @context = context.dup @visitor = context.visitor nodes.sort! { |a,b| compare_position a, b } @nodes = nodes end attr_reader :nodes protected :nodes def to_str if @nodes.empty? then '' else @visitor.visit(@nodes[0]).string_value end end def to_f to_string(@context).to_f end def true? not @nodes.empty? end def to_ruby @nodes end def self.def_comparison_operator(*ops) ops.each { |op| module_eval <<_, __FILE__, __LINE__ + 1 def #{op}(other) if other.is_a? XPathBoolean then other #{op} self.to_boolean else visitor = @visitor str = @context.make_string('') ret = false @nodes.each { |node| str.replace visitor.visit(node).string_value break if ret = (other #{op} str) } ret end end _ } end def_comparison_operator '==', '<', '>', '<=', '>=' class << self undef def_comparison_operator end def **(other) super unless other.is_a? XPathNodeSet merge other.nodes self end def count @nodes.size end def first @nodes[0] end def each(&block) @nodes.each(&block) end def funcall(name) # for XPointer raise "BUG" unless block_given? func = ('f_' + name.tr('-', '_')).intern super unless respond_to? func, true size = @nodes.size pos = 1 c = @context.dup begin @nodes.collect! { |node| c.reuse node, pos, size pos += 1 args = yield(c) send(func, node, *args) } rescue Object::ArgumentError if $@[1] == "#{__FILE__}:#{__LINE__-3}:in `send'" then raise XPath::ArgumentError, "#{$!} for `#{name}'" end raise end self end private def compare_position(node1, node2) visitor = @visitor ancestors1 = [] ancestors2 = [] p1 = visitor.visit(node1).parent while p1 ancestors1.push node1 p1 = visitor.visit(node1 = p1).parent end p2 = visitor.visit(node2).parent while p2 ancestors2.push node2 p2 = visitor.visit(node2 = p2).parent end unless node1 == node2 then raise XPath::Error, "can't compare the positions of given two nodes" end n = -1 ancestors1.reverse_each { |node1| node2 = ancestors2[n] unless node1 == node2 then break unless node2 return visitor.visit(node1).index - visitor.visit(node2).index end n -= 1 } ancestors1.size - ancestors2.size end def merge(other) if @nodes.empty? or other.empty? then @nodes.concat other elsif (n = compare_position(@nodes.last, other.first)) <= 0 then @nodes.pop if n == 0 @nodes.concat other elsif (n = compare_position(other.last, @nodes.first)) <= 0 then other.pop if n == 0 @nodes = other.concat(@nodes) else newnodes = [] nodes = @nodes until nodes.empty? or other.empty? n = compare_position(nodes.last, other.last) if n > 0 then newnodes.push nodes.pop elsif n < 0 then newnodes.push other.pop else newnodes.push nodes.pop other.pop end end newnodes.reverse! @nodes.concat(other).concat(newnodes) end end IteratorForAxis = { :self => SelfIterator.new, :child => ChildIterator.new, :parent => ParentIterator.new, :ancestor => AncestorIterator.new, :ancestor_or_self => AncestorOrSelfIterator.new, :descendant => DescendantIterator.new, :descendant_or_self => DescendantOrSelfIterator.new, :following => FollowingIterator.new, :preceding => PrecedingIterator.new, :following_sibling => FollowingSiblingIterator.new, :preceding_sibling => PrecedingSiblingIterator.new, :attribute => AttributeIterator.new, :namespace => NamespaceIterator.new, } def get_iterator(axis) ret = IteratorForAxis[axis] unless ret then raise XPath::NameError, "invalid axis `#{axis.id2name.tr('_','-')}'" end ret end def make_location_step if defined? @__lstep__ then @__lstep__ else @__lstep__ = LocationStep.new(@context) end end public def step(axis) iterator = get_iterator(axis) lstep = make_location_step lstep.set_iterator iterator oldnodes = @nodes @nodes = [] oldnodes.each { |node| lstep.reuse node nodes = yield(lstep).nodes nodes.reverse! if iterator.reverse_order? merge nodes } self end def select_all(axis) iterator = get_iterator(axis) visitor = @visitor oldnodes = @nodes @nodes = [] oldnodes.each { |start| nodes = [] iterator.each(start, visitor) { |i| nodes.push i.node } nodes.reverse! if iterator.reverse_order? merge nodes } self end def predicate context = @context size = @nodes.size pos = 1 result = nil newnodes = @nodes.reject { |node| context.reuse node, pos, size pos += 1 result = yield(context) break if result.is_a? Numeric not result } if result.is_a? Numeric then at result else @nodes = newnodes end self end def at(pos) n = pos.to_i if n != pos or n <= 0 then node = nil else node = @nodes[n - 1] end @nodes.clear @nodes.push node if node self end end class Context def initialize(node, namespace = nil, variable = nil, visitor = nil) visitor = TransparentNodeVisitor.new unless visitor @visitor = visitor @node = node @context_position = 1 @context_size = 1 @variables = variable @namespaces = namespace || {} end attr_reader :visitor, :node, :context_position, :context_size def reuse(node, pos = 1, size = 1) @variables = nil @node, @context_position, @context_size = node, pos, size end def get_variable(name) value = @variables && @variables[name] # value should be a XPathObjcect. raise XPath::NameError, "undefined variable `#{name}'" unless value value end PredefinedNamespace = { 'xml' => 'http://www.w3.org/XML/1998/namespace', } def get_namespace(prefix) ret = @namespaces[prefix] || PredefinedNamespace[prefix] raise XPath::Error, "undeclared namespace `#{prefix}'" unless ret ret end def make_string(str) XPathString.new str end def make_number(num) XPathNumber.new num end def make_boolean(f) if f then XPathTrue else XPathFalse end end def make_nodeset(*nodes) XPathNodeSet.new(self, *nodes) end def to_nodeset make_nodeset @node end def root_nodeset make_nodeset @visitor.visit(@node).root end def funcall(name, *args) begin send('f_' + name.tr('-', '_'), *args) rescue Object::NameError if $@[0] == "#{__FILE__}:#{__LINE__-2}:in `send'" then raise XPath::NameError, "undefined function `#{name}'" end raise rescue Object::ArgumentError if $@[1] == "#{__FILE__}:#{__LINE__-7}:in `send'" then raise XPath::ArgumentError, "#{$!} for `#{name}'" end raise end end private def must(type, *args) args.each { |i| unless i.is_a? type then s = type.name.sub(/\A.*::(?:XPath)?(?=[^:]+\z)/, '') raise XPath::TypeError, "argument must be #{s}" end } end def must_be_nodeset(*args) must XPathNodeSet, *args end def f_last make_number @context_size.to_f end def f_position make_number @context_position.to_f end def f_count(nodeset) must_be_nodeset nodeset make_number nodeset.count.to_f end def f_id(obj) unless obj.is_a? XPathNodeSet then ids = obj.to_str.strip.split(/\s+/) else ids = [] obj.each { |node| ids.push @visitor.visit(node).string_value } end root = @visitor.visit(@node).root make_nodeset(*@visitor.visit(root).select_id(*ids)) end def f_local_name(nodeset = nil) unless nodeset then n = @node else must_be_nodeset nodeset n = nodeset.first end n = @visitor.visit(n) if n n = n.name_localpart if n n = '' unless n make_string n end def f_namespace_uri(nodeset = nil) unless nodeset then n = @node else must_be_nodeset nodeset n = nodeset.first end n = @visitor.visit(n) if n n = n.namespace_uri if n n = '' unless n make_string n end def f_name(nodeset = nil) unless nodeset then n = @node else must_be_nodeset nodeset n = nodeset.first end n = @visitor.visit(n) if n n = n.qualified_name if n n = '' unless n make_string n end def f_string(obj = nil) obj = to_nodeset unless obj obj.to_string self end def f_concat(str, str2, *strs) s = str2.to_str.dup strs.each { |i| s << i.to_str } str.to_string(self).concat(s) end def f_starts_with(str, sub) make_boolean str.to_string(self).start_with?(sub.to_str) end def f_contains(str, sub) make_boolean str.to_string(self).contain?(sub.to_str) end def f_substring_before(str, sub) str.to_string(self).substring_before sub.to_str end def f_substring_after(str, sub) str.to_string(self).substring_after sub.to_str end def f_substring(str, start, len = nil) len = len.to_number(self) if len str.to_string(self).substring start.to_number(self), len end def f_string_length(str = nil) if str then str = str.to_string(self) else str = make_string(@node.string_value) end make_number str.size.to_f end def f_normalize_space(str = nil) if str then str = str.to_string(self) else str = make_string(@node.string_value) end str.normalize_space end def f_translate(str, from, to) str.to_string(self).translate from.to_str, to.to_str end def f_boolean(obj) obj.to_boolean self end def f_not(bool) make_boolean(!bool.true?) end def f_true make_boolean true end def f_false make_boolean false end def f_lang(str) lang = @visitor.visit(@node).lang make_boolean(lang && /\A#{Regexp.quote(str.to_str)}(?:-|\z)/i =~ lang) end def f_number(obj = nil) obj = to_nodeset unless obj obj.to_number self end def f_sum(nodeset) must_be_nodeset nodeset sum = 0.0 nodeset.each { |node| sum += make_string(@visitor.visit(node).string_value).to_f } make_number sum end def f_floor(num) num.to_number(self).floor end def f_ceiling(num) num.to_number(self).ceil end def f_round(num) num.to_number(self).round end end end xslt4r-0.0.2/test/ 40755 1750 0 0 7375044136 12560 5ustar michaelwheelxslt4r-0.0.2/test/data.xml100644 1750 0 375 7331337230 14265 0ustar michaelwheel Michael Neumann 21 Werner Maier 22 xslt4r-0.0.2/test/stylesheet.xsl100644 1750 0 3136 7332221520 15564 0ustar michaelwheel fdfd Test of XSLT num is num is num is neither 1 nor 2 SOOOOOOOOO xslt4r-0.0.2/test/stylesheet2.xsl100644 1750 0 4000 7331337230 15642 0ustar michaelwheel class Functions def initialize @txt = 34343 #"say hello" end def hello( context ) @txt end end class Elements def initialize @txt = 34343 #"say hello" end def hello( *a ) p "called" "hallllllllo" end end 1 2 3 4 5 56 6 7 87 8 puts "haljasflkasjflasjfaslfjaslkj" This is a configuration script Mode NONE Mode NORMAL

,

Mode SPEC
,
xslt4r-0.0.2/test/test.xml100644 1750 0 463 7331337230 14331 0ustar michaelwheel Dies ist ein deutscher Text This is an english text Dies ist ein deutscher Text xslt4r-0.0.2/test/test_style.xsl100644 1750 0 473 7331337230 15560 0ustar michaelwheel