From: Luke Graham Date: 2005-03-15T11:45:11+09:00 Subject: Re: tree transformation insight required 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 %% %% %% %<./jim // %hop% can hop %parentwidth% wide >% int %width% = %height%; } >% EXAMPLE XML DATA (input.xml as listed in the templates INPUT cmd) %},?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