MiniQuiz : Renesting Nodes (OWLScratch)

G

Gavin Kistner

--Apple-Mail-3--545942923
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
delsp=yes;
format=flowed

return unless bored? #MiniQuiz = do my 'work' for me.

I'm writing a library (OWLScratch) for converting wiki markup into
HTML. (The OWL part of the name is because it's derived from the
markup used by OpenWiki, hence "OpenWiki Language"; the Scratch is
because it's my own half-implemented flavor.)

It's not nearly ready yet, but the core concepts are working. So far,
it tokenizes the document into a series of hierarchically-nested
nodes. If I wanted XML, I could make up my own schema and be done.
But I want HTML.

In OWLScratch, the following represents a nested list:

* List item 1
* List item 2
* List item 2.1
* List item 2.2
* List item 3
* List item 3.1
* List item 3.1.1

Right now, that tokenizes into one node per line:
<bullet level="1">List item 1</bullet>
<bullet level="1">List item 2</bullet>
<bullet level="2">List item 2.1</bullet>
<bullet level="2">List item 2.2</bullet>
<bullet level="1">List item 3</bullet>
<bullet level="2">List item 3.1</bullet>
<bullet level="3">List item 3.1.1</bullet>

The challenge is that I need to be able to spin through the list and
(should be possible in one pass) properly nest those as the HTML
requires:

<ul> <-- new node!
<li>List item 1</li>
<li>List item 2
<ul> <-- new node, child of list item 2!
<li>List item 2.1</li>
<li>List item 2.2</li>
</ul> <-- closed because the next item is a lower level
<li>List item 3
<ul> <-- holy crap, it happened again!
<li>List item 3.1
<ul> <-- when will the madness end?
<li>List item 3.2</li>
</ul>
</li>
</ul>
</li>
</ul>

I know this should be not-hard, but I just played a full game of
Ultimate Frisbee, and I thought this might appeal to someone else.
The full library (so far, v0.0.1 or so) is attached, but I can (and
probably will have to) extend the Tag class to support DOM-like
properties such as previous_sibling and next_sibling. And make
reparenting a node properly remove it from the previous parent's
@child_nodes collection.

I'll speak more about OWLScratch in a few days, when I hope to have
it ready for a really preliminary release.

Oh, for extra credit - think about nested list types, as seen here:
http://openwiki.com/ow.asp?HelpOnFormatting#h9
(My lists do not (currently) allow content of a single item to be
manually wrapped onto multiple lines.)



--Apple-Mail-3--545942923
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-ruby-script;
x-unix-mode=0644;
name="OWLScratch.rb"
Content-Disposition: attachment;
filename=OWLScratch.rb

require 'strscan'=0D
=0D
#todo - override html conversion per factory=0D
#todo - pass after scanning to properly reparent lists.=0D
#todo - but I also like the name OWLScribble...no bird-tie in, but more =
fun=0D
class OWLScratch=0D
=0D
class TagFactory=0D
def self.by_type=0D
@by_type ||=3D {}=0D
end=0D
=0D
attr_reader :tag_name, :eek:pen_match, :close_match, =
:eek:pen_requires_bol, :close_requires_bol, :autoclose, :type, =
:allowed_type=0D
def initialize( tag_name, options=3D{} )=0D
@tag_name =3D tag_name=0D
[ :eek:pen_match, :close_match,=0D
:eek:pen_requires_bol, :close_requires_bol,=0D
:allowed_type, :autoclose,=0D
:text, :attrs, :setup, :type ].each{ |k|=0D
self.instance_variable_set( :"@#{k}", =
options[ k ] )=0D
}=0D
( self.class.by_type[ @type ] ||=3D [] ) << self =
if @type=0D
end=0D
=0D
def match( ss )=0D
return nil unless ( !@open_requires_bol || =
ss.bol? ) && ss.scan( @open_match )=0D
tag =3D Tag.new( @tag_name, self )=0D
@setup.call( tag, ss ) if @setup=0D
tag=0D
end=0D
end=0D
=0D
class Tag=0D
attr_accessor :tag_name, :child_nodes, :attrs, =
:parent_node=0D
=0D
def initialize( tag_name, owning_factory )=0D
@tag_name =3D tag_name=0D
@owning_factory =3D owning_factory=0D
@child_nodes =3D [ ]=0D
@attrs =3D { }=0D
end=0D
=0D
def type=0D
@owning_factory.type=0D
end=0D
=0D
def close_match=0D
@owning_factory.close_match=0D
end =0D
=0D
def close_requires_bol?=0D
@owning_factory.close_requires_bol=0D
end=0D
=0D
def autoclose?=0D
@owning_factory.autoclose=0D
end=0D
=0D
def allowed_type=0D
@owning_factory.allowed_type=0D
end=0D
=0D
def append_child( node )=0D
puts "#{self.inspect}.append_child( =
#{node.inspect} )" if $DEBUG=0D
@child_nodes << node=0D
node.parent_node =3D self=0D
node=0D
end=0D
=0D
def << ( str )=0D
last_child =3D @child_nodes.last=0D
if TextNode =3D=3D=3D last_child=0D
last_child << str=0D
else=0D
append_child( TextNode.new( str ) )=0D
end=0D
end=0D
=0D
def to_html=0D
out =3D "<#{@tag_name}"=0D
@attrs.each{ |k,v| out << " =
#{k}=3D\"#{v.to_s.gsub( '""', '&quot;' )}\"" }=0D
if @child_nodes.empty?=0D
out << ' />'=0D
else=0D
out << '>'=0D
@child_nodes.each{ |node|=0D
out << node.to_html=0D
}=0D
out << "</#{@tag_name}>"=0D
end=0D
out << "\n" unless @owning_factory.type =3D=3D =
:inline || @owning_factory.type =3D=3D :td=0D
out=0D
end=0D
=0D
def inspect=0D
"<#{@tag_name}:#{@type}:#{@allowed_type}>"=0D
end=0D
end=0D
=0D
class TextNode=0D
attr_accessor :parent_node=0D
=0D
def initialize( node_value=3D'' )=0D
@node_value =3D node_value=0D
end=0D
=0D
def << ( str )=0D
@node_value << str=0D
end=0D
=0D
def inspect=0D
"<TextNode '#{@node_value}'>"=0D
end=0D
=0D
def to_html=0D
@node_value.htmlsafe!=0D
end=0D
=0D
end=0D
=0D
TagFactory.new( :wiki_command,=0D
:type =3D> :block,=0D
:eek:pen_match =3D> =
/##(TableOfContents|DEPRECATED|BALETED|IncludePage\(\s*(.+)\s*\))##/, =
:eek:pen_requires_bol =3D> true,=0D
:setup =3D> lambda{ |tag, ss|=0D
tag.attrs[ :do ] =3D ss[1][ /[a-z]+/i ]=0D
tag.attrs[ :param ] =3D ss[2] if ss[2]=0D
},=0D
:autoclose =3D> true=0D
)=0D
TagFactory.new( :heading,=0D
:type =3D> :block,=0D
:eek:pen_match =3D> /(=3D{1,6}) +(.+) +\1[ \t]*\n/, =
:eek:pen_requires_bol =3D> true,=0D
:setup =3D> lambda{ |tag, ss|=0D
tag.attrs[ :level ] =3D ss[1].length=0D
tag << ss[2]=0D
},=0D
:autoclose =3D> true=0D
)=0D
TagFactory.new( :hr,=0D
:type =3D> :block,=0D
:eek:pen_match =3D> /-{4,} *\n/, :eek:pen_requires_bol =3D> =
true,=0D
:autoclose =3D> true=0D
)=0D
TagFactory.new( :bullet,=0D
:type =3D> :block,=0D
:eek:pen_match =3D> /(( )+) ?\* /, :eek:pen_requires_bol =3D> =
true,=0D
:close_match =3D> /\n/,=0D
:setup =3D> lambda{ |tag, ss| tag.attrs[ :level ] =3D =
ss[ 1 ].length / 2 },=0D
:allowed_type =3D> :inline=0D
)=0D
TagFactory.new( :numberlist,=0D
:type =3D> :block,=0D
:eek:pen_match =3D> /(( )+) ?\d+\. /, :eek:pen_requires_bol =
=3D> true,=0D
:close_match =3D> /\n/,=0D
:setup =3D> lambda{ |tag,ss| tag.attrs[ :level ] =3D =
ss[1].length / 2 },=0D
:allowed_type =3D> :inline=0D
)=0D
TagFactory.new( :alphalist,=0D
:type =3D> :block,=0D
:eek:pen_match =3D> /(( )+) ?[a-z]\. /, :eek:pen_requires_bol =
=3D> true,=0D
:close_match =3D> /\n/,=0D
:setup =3D> lambda{ |tag,ss| tag.attrs[ :level ] =3D =
ss[1].length / 2 },=0D
:allowed_type =3D> :inline=0D
)=0D
TagFactory.new( :dl,=0D
:type =3D> :block,=0D
:eek:pen_match =3D> /(?=3D( |\t)+; .+ : )/, =
:eek:pen_requires_bol =3D> true,=0D
:close_match =3D> /(?!( |\t)+; .+ : )/, =
:close_requires_bol =3D> true,=0D
:allowed_type =3D> :deflist=0D
)=0D
TagFactory.new( :pre,=0D
:type =3D> :block,=0D
:eek:pen_match =3D> /^([ \t]*)\{\{\{\n(.+?)\n\1\}\}\}\n/m, =
:eek:pen_requires_bol =3D> true,=0D
:close_match =3D> /\n/,=0D
:setup =3D> lambda{ |tag,ss| tag << ss[2].gsub( =
/^#{ss[1]}/, '' ) },=0D
:autoclose =3D> true=0D
)=0D
TagFactory.new( :table,=0D
:type =3D> :block,=0D
:eek:pen_match =3D> /(?=3D\|\|)/, :eek:pen_requires_bol =3D> =
true,=0D
:close_match =3D> /(?=3D[^|])/, :close_requires_bol =3D> =
true,=0D
:allowed_type =3D> :table=0D
)=0D
TagFactory.new( :p,=0D
:type =3D> :block,=0D
:eek:pen_match =3D> /(( )+) ?: /, :eek:pen_requires_bol =3D> =
true,=0D
:close_match =3D> /\n/,=0D
:setup =3D> lambda{ |tag,ss| tag.attrs[ :indent ] =3D =
ss[1].length / 2 },=0D
:allowed_type =3D> :inline=0D
)=0D
# The paragraph is the catch-all for blocks;=0D
# it must appear after all other block factories=0D
TagFactory.new( :p,=0D
:type =3D> :block,=0D
:eek:pen_match =3D> /(?=3D\S)/, :eek:pen_requires_bol =3D> =
true,=0D
:close_match =3D> /\n\n/,=0D
:allowed_type =3D> :inline=0D
)=0D
=0D
=0D
TagFactory.new( :tr,=0D
:type =3D> :table,=0D
:eek:pen_match =3D> /(?=3D\|\|)/, :eek:pen_requires_bol =3D> =
true,=0D
:close_match =3D> /\|\|[ \t]*\n/,=0D
:allowed_type =3D> :td=0D
)=0D
TagFactory.new( :td,=0D
:type =3D> :td,=0D
:eek:pen_match =3D> /((?:\|\|)+)\s*/,=0D
:close_match =3D> /(?=3D\s*\|\|)/,=0D
:setup =3D> lambda{ |tag,ss|=0D
colspan =3D ss[1].length / 2=0D
tag.attrs[ :colspan ] =3D colspan unless =
colspan < 2=0D
},=0D
:allowed_type =3D> :inline=0D
)=0D
=0D
=0D
TagFactory.new( :dt,=0D
:type =3D> :deflist,=0D
:eek:pen_match =3D> /( |\t)+; /, :eek:pen_requires_bol =3D> =
true,=0D
:close_match =3D> / : /,=0D
:allowed_type =3D> :inline=0D
)=0D
TagFactory.new( :dd,=0D
:type =3D> :deflist,=0D
:eek:pen_match =3D> /(?=3D.)/,=0D
:close_match =3D> /\n/,=0D
:allowed_type =3D> :inline=0D
)=0D
=0D
=0D
TagFactory.new( :b,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> /\*\*/, :close_match =3D> /\*\*/,=0D
:allowed_type =3D> :inline=0D
)=0D
TagFactory.new( :i,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> /\/\//, :close_match =3D> /\/\//,=0D
:allowed_type =3D> :inline=0D
)=0D
TagFactory.new( :strike,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> /--/, :close_match =3D> /--/,=0D
:allowed_type =3D> :inline=0D
)=0D
TagFactory.new( :sup,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> /\^\^/, :close_match =3D> /\^\^/,=0D
:allowed_type =3D> :inline=0D
)=0D
TagFactory.new( :sub,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> /__/, :close_match =3D> /__/,=0D
:allowed_type =3D> :inline=0D
)=0D
TagFactory.new( :tt,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> /@@([^\n]+?)@@/,=0D
:setup =3D> lambda{ |tag,ss| tag << ss[1] },=0D
:autoclose =3D> true=0D
)=0D
TagFactory.new( :tt,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> /\{\{\{([^\n]+?)\}\}\}/,=0D
:setup =3D> lambda{ |tag,ss| tag << ss[1] },=0D
:autoclose =3D> true=0D
)=0D
TagFactory.new( :todo,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> /!!([a-z].+?)!!/i,=0D
:setup =3D> lambda{ |tag,ss| tag << ss[1] },=0D
:autoclose =3D> true=0D
)=0D
TagFactory.new( :a,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> /(?:HTTP|FTP|HTTPS):\/\/\S+/,=0D
:setup =3D> lambda{ |tag,ss|=0D
tag.attrs[ :href ] =3D ss[0]=0D
tag << ss[0]=0D
},=0D
:autoclose =3D> true=0D
)=0D
TagFactory.new( :a,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> /\[((?:HTTP|FTP|HTTPS):\/\/\S+) =
([^\]]+)\]/,=0D
:setup =3D> lambda{ |tag,ss|=0D
tag.attrs[ :href ] =3D ss[1]=0D
tag << ss[2]=0D
},=0D
:autoclose =3D> true=0D
)=0D
TagFactory.new( :wiki_link,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> =
/[A-Z]{2,}[a-z][a-zA-Z]*|[A-Z][a-z]+[A-Z][a-zA-Z]*/,=0D
:setup =3D> lambda{ |tag,ss|=0D
tag.attrs[ :page ] =3D ss[0]=0D
tag << ss[0].dewikiword=0D
},=0D
:autoclose =3D> true=0D
)=0D
TagFactory.new( :wiki_link,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> /\[\[([^\]\n]{2,}?)\]\]/,=0D
:setup =3D> lambda{ |tag,ss|=0D
tag.attrs[ :page ] =3D ss[0]=0D
tag << ss[0]=0D
},=0D
:autoclose =3D> true=0D
)=0D
TagFactory.new( :wiki_link,=0D
:type =3D> :inline,=0D
:eek:pen_match =3D> =
/\[([A-Z]{2,}[a-z][a-zA-Z]*|[A-Z][a-z]+[A-Z][a-zA-Z]*) ([^\]]+)\]/,=0D
:setup =3D> lambda{ |tag,ss|=0D
tag.attrs[ :page ] =3D ss[1]=0D
tag << ss[2]=0D
},=0D
:autoclose =3D> true=0D
)=0D
=0D
def initialize( owl_string )=0D
@ss =3D StringScanner.new( owl_string )=0D
=0D
@root =3D Tag.new( :root, TagFactory.new( :root, :type =
=3D> :root, :allowed_type =3D> :block ) )=0D
@current =3D @root=0D
=0D
#todo - preparse de-html of invalid tags=0D
while [email protected]?=0D
puts "Step with @current =3D #{@current.inspect} =
: #{(@ss.peek(20)+'...').inspect}" if $DEBUG=0D
=0D
# Keep popping off the current tag until we get =
to the root,=0D
# as long as the end criteria is met=0D
while ( @current !=3D @root ) && =
([email protected]_requires_bol? || @ss.bol?) && @ss.scan( =
@current.close_match ) =0D
@current =3D @current.parent_node || =
@root=0D
end=0D
=0D
# No point in continuing if closing out tags =
consumed the rest of the string=0D
break if @ss.eos?=0D
=0D
# Look for a tag to open=0D
tag =3D nil=0D
TagFactory.by_type[ @current.allowed_type =
].each{ |factory|=0D
if tag =3D factory.match( @ss )=0D
@current.append_child( tag )=0D
@current =3D tag unless =
tag.autoclose?=0D
break=0D
end=0D
}=0D
next if tag #found one, start over=0D
=0D
# Couldn't find a valid tag at this spot=0D
# so we need to eat some characters=0D
if @ss.scan( =
/~([A-Z]{2,}[a-z][a-zA-Z]*|[A-Z][a-z]+[A-Z][a-zA-Z]*|\[\[([^\]\n]{2,}?)\]\=
]|\[(?:[A-Z]{2,}[a-z][a-zA-Z]*|[A-Z][a-z]+[A-Z][a-zA-Z]*) [^\]]+\])/ )=0D=

# Shove negated links directly as text=0D=

consumed =3D @ss[1]=0D
else=0D
# Man, it would be nice if I could do a =
lookahead here=0D
# and consume more than a few characters =
at a time!=0D
=0D
#Hopefully no opening or closing tags =
are based on letters or changes in tab/spaces;=0D
#if that's not true, the next line must =
be foregone in favor of the (slow)=0D
#one-char-at-a-time consumption=0D
consumed =3D @ss.scan( /[a-z \t]+|./m )=0D=

#consumed =3D @ss.scan( /[a-z]+|[ =
\t]+|./m )=0D
#consumed =3D @ss.getch=0D
end=0D
@current << consumed if @current.allowed_type =3D=3D=
:inline=0D
end=0D
=0D
end=0D
=0D
def to_s=0D
out =3D ''=0D
@root.child_nodes.each{ |el|=0D
out << el.to_html=0D
}=0D
out=0D
end=0D
=0D
end=0D
=0D
class String=0D
def htmlsafe=0D
self.dup.htmlsafe!=0D
end=0D
=0D
def htmlsafe!=0D
self.gsub!( /&/, '&amp;' )=0D
self.gsub!( /</, '&lt;' )=0D
self.gsub!( />/, '&gt;' )=0D
self=0D
end=0D
=0D
def dewikiword=0D
self.gsub( /([a-z])([A-Z])/, '\\1 \\2' )=0D
end=0D
end=0D
=0D
if __FILE__ =3D=3D $0=0D
str =3D <<ENDTEXT=0D
=0D
##TableOfContents##=0D
=0D
=3D Welcome to the OWLScribble =3D=0D
//An introduction to the markup.//=0D
=0D
=3D=3D What is OWLScribble? =3D=3D=0D
~OWLScribble is a wiki markup language //based on// the markup used by =
[HTTP://www.openwiki.org/ OpenWiki]. It was designed for use with the =
~SewWiki project by GavinKistner.=0D
=0D
=3D=3D Supported Markup =3D=3D=0D
=3D=3D=3D Basic Inline Styling =3D=3D=3D=0D
{{{=0D
This text **is bold**, this //is italic//, and --this has been struck--.=0D=

=0D
This is a @@code reference@@, as is {{{this text}}}.=0D
=0D
A double-exclamation point is a special 'todo' item. !!Add more =
examples!!=0D
=0D
You can also ^^superscript^^ and __subscript__ text, like H__2__O or =
e^^pi*i^^.=0D
}}}=0D
This text **is bold**, this //is italic//, and --this has been =
striked--.=0D
=0D
This is a @@code reference@@, as is {{{this text}}}.=0D
=0D
A double-exclamation point is a special 'todo' item. !!Add more =
examples!!=0D
=0D
You can also ^^superscript^^ and __subscript__ text, like H__2__O or =
e^^pi*i^^.=0D
=0D
* //Unlike ~OpenWiki, you are not allowed to underline text. Sorry, =
but underlining is reserved for links in hyperlinked documents.//=0D
=0D
=3D=3D=3D Headings =3D=3D=3D=0D
{{{=0D
=3D Heading Level 1 =3D=0D
=3D=3D Heading Level 2 =3D=3D=0D
=3D=3D=3D Heading Level 3 =3D=3D=3D=0D
=3D=3D=3D=3D Heading Level 4 =3D=3D=3D=3D=0D
=3D=3D=3D=3D=3D Heading Level 5 =3D=3D=3D=3D=3D=0D
=3D=3D=3D=3D=3D=3D Heading Level 6 =3D=3D=3D=3D=3D=3D=0D
}}}=0D
Headings must begin at the start of the line. Markup inside headings is =
ignored. (So, for example, {{{=3D=3D My //Sweet// Heading =3D=3D}}} will =
show the {{{//}}} characters in the output.)=0D
=0D
=3D=3D=3D Linking =3D=3D=3D=0D
!!Put content here!!=0D
=0D
=3D=3D=3D Lists =3D=3D=3D =0D
=0D
=3D=3D=3D=3D Bulleted Lists =3D=3D=3D=3D=0D
{{{=0D
1 2 3 4=0D
1234567890123456789012345678901234567890=0D
* Bulleted lists must have **two** spaces per indent level.=0D
* **A space** must appear after the asterisk.=0D
* They may be nested to an arbitrary depth.=0D
}}}=0D
=0D
* Bulleted lists must have **two** spaces per indent level.=0D
* **A space** must appear after the asterisk.=0D
* They may be nested to an arbitrary depth.=0D
=0D
=3D=3D=3D=3D Numbered Lists =3D=3D=3D=3D=0D
{{{=0D
1 2 3 4=0D
1234567890123456789012345678901234567890=0D
1. Numbered lists must have **two** spaces per indent level.=0D
1. The actual number doesn't matter=0D
3. But the period after the number does.=0D
1. As well as the space after the period.=0D
1. All lists types can be nested.=0D
}}}=0D
=0D
1. Numbered lists must have **two** spaces per indent level.=0D
1. The actual number doesn't matter=0D
3. But the period after the number does.=0D
1. As well as the space after the period.=0D
1. All lists types can be nested.=0D
=0D
=3D=3D=3D=3D Letter Lists =3D=3D=3D=3D=0D
!!Put content here!!=0D
=0D
=3D=3D=3D=3D Definition Lists =3D=3D=3D=3D=0D
!!Put content here!!=0D
=0D
* !!Note no nesting!!=0D
=0D
=3D=3D=3D=3D Mixing Lists =3D=3D=3D=3D=0D
!!Put content here!!=0D
=0D
=3D=3D=3D Tables =3D=3D=3D =0D
|| **Age** || **Sex** || **Weight** ||=0D
|| 32 || M || 180 ||=0D
|| 30 || F || 150 ||=0D
|||| average || 165 ||=0D
=0D
=3D=3D Processing Directives =3D=3D=0D
!!Put content here!!=0D
=0D
##IncludePage(ProcessingDirectives)##=0D
=0D
=0D
=3D=3D Miscellaneous =3D=3D=0D
HTML entities are not needed (and do not work) inside OWLScribble; you =
can type {{{"this & that"}}} and it will produce the HTML {{{this &amp; =
that}}}, displaying as "this & that". Typing {{{&amp;}}} will actually =
show "&amp;".=0D
=0D
Finally, we end with a few big paragraph blocks with no styling in them =
at all, for speed testing purposes:=0D
=0D
: Dry dog food is far better than canned! It is more economical, takes =
up less space, and is generally better tasting. With reconstituted dried =
milk (and sugar if you like) most dry food tastes not too different from =
dry breakfast cereal. A hundred pound sack of dry dog food contains as =
many calories as a ton of fresh potatoes. The dog food also contains =
protein, vitamins, etc., that the potatoes do not.=0D
=0D
: Our understanding of the rubber bag has led us to an effective =
tool that accurately indicates whether too much, too little, or just the =
right amount of food is going in. In the last chapter you've learned how =
to work that tool, integrating it into your daily and monthly routine so =
the information it yields can guide your eating.=0D
=0D
: All the information in the world, however, doesn't change a thing =
until somebody takes action based upon it. In losing weight, "somebody" =
is your body. Now we'll turn to planning meals to control the calories =
that go in. Analysis of the trend based on daily weight measurements is =
the key engineering trick to weight control. Meal planning for =
predictable calorie intake is the central management tool which closes =
the circle and achieves control over weight.=0D
=0D
: The goal of meal planning is a predictable and reliable daily =
calorie intake. We can't really wear an eat watch to tell us when to =
stop eating, but we can accomplish the same objective with a little =
paperwork in advance. By planning meals then sticking to the plan, =
you're not only guaranteed to achieve your goal, you eliminate the =
uncertainty about meals and the need for on-the-fly judgements about =
what, when, and how much to eat that are a prime contributor to weight =
gain in people living stressful, chaotic lives.=0D
=0D
: Planning meals in advance may seem foreign; an act that stamps out =
some of the precious spontaneity that makes life enjoyable. I think =
you'll see the reality isn't that bad, but first consider why planning =
meals is worth discussing at all. Eating is important; it's one of very =
few things in life that isn't optional. If you don't eat, you die. If =
you eat too much for too long, you die. You wouldn't consider for a =
moment investing in a company that had no budgets, where everybody said, =
"We just spend whatever we feel like from day to day, and hope it will =
all work out in the long run." Not only would such a business be prone =
to bankruptcy, its managers would have no way of knowing where the money =
was going; there'd be no way to measure actual performance against goals =
to discover where problems lay. No, only a fool would risk his money on =
such a venture.=0D
=0D
: Yet by trying to "wing it" with regard to what you eat, to balance =
your long term calorie intake meal by meal, making every decision on the =
spur of the moment, you're placing something even more precious than =
your money, your own health, in the hands of a process you know =
inevitably leads to serious trouble.=0D
=0D
: You encounter, in business, the rare exceptions: managers who can =
run a small to medium sized business without a budget or a plan. They =
are "naturals," endowed either with a talent for assimilating vast =
quantities of detail and extracting the meaning within, or else with a =
sixth sense for emerging problems and an instinct for solving them. =
These rare individuals, born with a "sense for business," are the =
managerial equivalent of people with a built-in eat watch like Skinny =
Sam. They can get along without the help of the numbers and calculations =
the rest of us need to steer a steady course.=0D
=0D
: So it is with weight control. Just because some people manage =
without planning their meals doesn't mean it'll work for you or me. We =
must, like most managers in business, supplement our unreliable =
instincts with numbers that chart our goal and guide us there.=0D
=0D
: In business, a budget collapses a huge amount of de- tail, the =
individual transactions, into a small collection of numbers: how much =
money is allocated to various general purposes. In planning meals, all =
the multitude of foods and the infinite variety of meals are similarly =
reduced to a single number: calories per day. To plan meals, it's =
essential to know how many calories per day you're trying to eat. Where =
does that number come from?=0D
=0D
: As you gain more and more experience monitoring and controlling your =
weight, you'll collect enough information to know precisely how many =
calories your own body needs per day. Until then, you can start with =
guidelines for people about like you. Based on your height, frame size, =
and sex look up the calories burned per day in the tables on pages 36 =
and 37. Pick a number in the middle of the range given. For example, =
Dietin' Doris, five foot four in her bare feet with an average build, =
would start with a calorie target of 1770. (The range in the table runs =
from 1574 to 1967, and the average of these numbers is (1574 + 1967) 2 =3D=
1770.)=0D
=0D
: This target assumes Doris' goal is maintaining her present weight. =
If she wants to lose or gain weight, it must be adjusted based on the =
daily calorie shortfall or excess she intends. To lose weight at the =
rate of one pound per week, Doris should eat 500 fewer calories per day =
than she burns. (Thus, over a week she'll end up 3500 calories shy and =
hence burn off 3500 calories of fat: one pound.)=0D
=0D
: Subtracting the calorie cutback, 500, from the number she burns =
gives the number she can eat per day. Her calorie target is thus 1770 =
500 =3D 1270 calories per day.=0D
ENDTEXT=0D
=0D
#$DEBUG =3D true=0D
require 'benchmark'=0D
parser, out =3D nil=0D
Benchmark.bm( 10 ){ |x|=0D
x.report( "Node Tree" ){=0D
parser =3D OWLScratch.new( str )=0D
}=0D
x.report( "To HTML" ) {=0D
out =3D parser.to_s=0D
}=0D
}=0D
puts out=0D
end=0D

--Apple-Mail-3--545942923--
 
A

Aleksi

Gavin said:
In OWLScratch, the following represents a nested list:

* List item 1
* List item 2
* List item 2.1
* List item 2.2
* List item 3
* List item 3.1
* List item 3.1.1

Right now, that tokenizes into one node per line:
<bullet level="1">List item 1</bullet>
<bullet level="1">List item 2</bullet>
<bullet level="2">List item 2.1</bullet>
<bullet level="2">List item 2.2</bullet>
<bullet level="1">List item 3</bullet>
<bullet level="2">List item 3.1</bullet>
<bullet level="3">List item 3.1.1</bullet>

The challenge is that I need to be able to spin through the list and
(should be possible in one pass) properly nest those as the HTML requires:

How about something like:

bullets = [[1, "List Item 1."],
[1, "List Item 2."],
[2, "List Item 2.1"],
[2, "List Item 2.2"],
[1, "List Item 3."],
[2, "List Item 3.1"],
[3, "List Item 3.1.1"]
]

def indent(level, text)
puts " "*level + text
end

current_level = 0
bullets.each do |level, text|
indent((current_level+=1)-1, "<ul>" ) while level > current_level
indent( current_level-=1, "</ul>") while level < current_level
indent( current_level, "<li>#{text}</li>")
end
indent(current_level-=1, "</ul>") while 0 < current_level

Now, I'm sure you don't want the output in text, so perhaps you like to
replace indent(level, "text") with insert("<node>"), but the structure
might not be that different.

- Aleksi
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,152
Members
46,698
Latest member
LydiaHalle

Latest Threads

Top