From: Luke Graham Date: 2005-03-17T15:14:54+09:00 Subject: Re: tree transformation insight required Ive just discovered XSLT templates can take parameters... which is the thing Ive just added to my templating language that I thought they couldnt do. Im going to have a go at using XSLT to do this. On Tue, 15 Mar 2005 12:56:35 +1000, Luke Graham wrote: > Fixed the bug... > Change the argument line in cmdlexer to > [%r{\A[^\s%]+},?a ], # Argument > and change > file = File.new(filename + ".xml", File::RDONLY) > to > file = File.new(filename, File::RDONLY) > > > On Tue, 15 Mar 2005 12:44:53 +1000, Luke Graham wrote: > > Steve, > > > > thanks for your reply. I ended up creating a little template language instead. > > Heres the code if youre interested. I can post it to RAA/Rubyforge/whatever > > if people are finding it useful, so if you do use it, send me an email please. > > Lexer is the LittleLexer package written by John Carter, and can be found > > at http://littlelexer.rubyforge.org/ > > > > Known bug: placing a '.' in the INPUT line (and possibly elsewhere) confuses > > the parser. > > > > EXAMPLE TEMPLATE > > %% COMMENT This is a comment and will be ignored %% > > %% INPUT input %% > > > > % > // %hop% %hop% > > >% > > > > % > void %tagname% { > > %<./jim // %hop% can hop %parentheight% high >% > > %<./jim // %hop% can hop %parentwidth% wide >% > > int %width% = %height%; > > } > > >% > > > > EXAMPLE XML DATA (input.xml as listed in the templates INPUT cmd) > > > > > > > > > > > > > > > > > > > EXAMPLE OUTPUT > > // bunny bunny > > > > // rabbity rabbity > > > > void bar { > > // bunny can hop 11 high > > // bunny can hop bb wide > > int bb = 11; > > } > > > > void bar2 { > > // rabbity can hop 21 high > > // rabbity can hop rr wide > > int rr = 21; > > } > > > > CODE > > #!/usr/bin/ruby > > > > require "rexml/document" > > include REXML # namespace > > require "digest/md5" > > require "ftools" > > require "Lexer" > > > > commands = ["COMMENT", "INPUT"] > > $inputfiles = [] > > > > $lexer = Lexer.new [ > > [%r{\A\s*%%\s*(#{commands.join "|"})[^%]*%%},?i ], # Command + args > > [%r{\A%<\s*[^\s]*},?o ], # Open brace and xpath > > [%r{\A>%},?c ], # Close brace > > [%r{\A%\w+%},?m ], # Macro > > [%r{\A[^%??]*},?j ], # Junk > > [%r{\A\s+}m, ?\s], # Whitespace ' ' > > ] > > > > $cmdlexer = Lexer.new [ > > [%r{\A\s*%%\sINPUT\s},?i ], # Input > > [%r{\A\s*%%\sCOMMENT\s},?c ], # Input > > [%r{\A\s*%%},?e ], # End of cmd > > [%r{\A\w+},?a ], # Argument > > [%r{\A\s+}m, ?\s], # Whitespace > > ] > > > > $xpathlexer = Lexer.new [ > > [%r{\A%<}mx,?o ], # Open brace > > [%r{\A\s*[^\s]+\s*},?x ], # Anything else is part of the xpath > > [%r{\A\s+}m, ?\s], # Whitespace > > ] > > > > def expand(string, node) > > braces = [] > > inputfiles = [] > > xpath = [] > > expansion = "" > > index = 0 > > > > tokenstr, result = $lexer.scan string > > tokens = tokenstr.split("") > > > > while index < tokens.size > > begin > > token = tokens[index] > > case token > > when 'i' # Command > > ctokens, cresult = $cmdlexer.scan result[index] > > cmode = false > > ctokens.split("").each_with_index { |t,i| > > (cmode = t; next) if t != 'a' > > case cmode > > when 'i' # Include xml data file > > inputfiles << cresult[i] > > end > > } > > when 'o' # Open brace > > next if !node > > # Xpath > > xtokens, xresult = $xpathlexer.scan result[index] > > xtokens.split("").each_with_index { |t,i| > > xpath << xresult[i] if t == 'x' > > } > > braces << index + 1 > > when 'c' # Close brace > > next if !node > > return expansion if node and braces.empty? > > brace = braces.pop > > tmp = result.indices(brace..index).join "" > > node.elements.each(xpath.pop) { |e| > > expansion << expand(tmp, e) > > } > > when 'm' # Macro > > next if !node or !braces.empty? > > attrname = result[index].gsub(/%/, "") > > search = (attrname =~ /^parent/ ? (attrname.gsub! > > /^parent/,""; node.parent) : node) > > if attrname == "tagname" > > expansion << search.name > > elsif search.attributes.key? attrname > > expansion << search.attributes[attrname] > > else > > puts "No such attribute #{attrname} in #{search}" > > expansion << result[index] > > end > > when 'j' # Junk > > next if !node or !braces.empty? > > expansion << result[index] > > end > > ensure > > index += 1 > > end > > end > > > > return (node ? expansion : inputfiles) > > end > > > > # Pass 1 extracts input commands > > (puts "Please supply a template file on the cmd line"; exit) if !$*[0] > > puts "First pass" > > inputfiles = expand IO.readlines($*[0]).to_s, nil > > > > # Pass 2 expands the template > > puts "Second pass" > > inputfiles.each { |filename| > > file = File.new(filename + ".xml", File::RDONLY) > > document = Document.new file > > file.close > > outfile = File.new(filename + ".out", File::CREAT|File::TRUNC|File::WRONLY) > > outfile << expand(IO.readlines($*[0]).to_s, document) > > outfile.close > > } > > > > On Sun, 13 Mar 2005 02:51:39 +0900, Steven Jenkins > > wrote: > > > Luke Graham wrote: > > > > Hi list, > > > > > > > > I was wondering if anyone would have some insight into a problem Ive got. > > > > > > > > Im taking an xml document (looks very much like a w3c schema with some > > > > extra attributes) and transforming it into c++ code. The method I use looks > > > > something like... > > > > > > > > def recurse(file, func, element) > > > > case func > > > > when "declare" > > > > file << element.attributes["type"] << element.attributes["name"] > > > > when "instantiate" > > > > file << element.attributes["name"] << " = new " << element.attributes["type"] > > > > do wierd stuff based on type and state > > > > end > > > > element.each_child { |c| > > > > recurse file, func, c > > > > } > > > > end > > > > > > > >[....] > > > > > > > > Id like to get the logic out of the recurse statement for three reasons. > > > > First of all, Im starting recurse up more times than Ive shown > > > > above, because I have four separate sets of header/cpp templates, > > > > not to mention I would like to add other templates that arent c++ code. > > > > It would be good to know that each case could be handled safely > > > > without interfering with each others output and special cases. > > > > Secondly, someone else would like to write their own templated > > > > code, and their required output would be different. Thirdly, recurse is > > > > simply becoming scary. > > > > > > I've done something similar to your first alternative, but using blocks. > > > That's somewhat more Rubyesque, IMO. (This is not for XML, but the > > > idea's the same.) > > > > > > def descend_with_indicators(depth, &block) > > > yield :item, depth, self.item > > > unless children.empty? > > > yield :down, depth, nil > > > children.each do |c| > > > c.descend_with_indicators(depth + 1, &block) > > > end > > > yield :up, depth, nil > > > end > > > end > > > > > > Invoke it as > > > > > > root.descend_with_indicators(0) do |ind, dpth, item| > > > # ind will be one of :down, :item, or :up > > > # :down and :up indicate change in depth > > > # :item indicates an actual node in the tree > > > # if you don't need depth, you can omit it everywhere > > > end > > > > > > Steve > > > > > > > > > > -- > > spooq > > > > -- > spooq > -- spooq