From: Luke Graham Date: 2005-03-15T11:56:53+09:00 Subject: Re: tree transformation insight required 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