bpublish v1.41 (c) 2002 Bret Victor bret@ugcs.caltech.edu http://www.ugcs.caltech.edu/~bret ____________________ Table of Contents: 1. Introduction 2. Installation 3. User's Automatic 3.1 The Basics 3.2 Example bpub File 3.3 Example bpk File 4. User's Manual 4.1 Command Line Syntax 4.2 Bpub Syntax 4.3 Bpub Core Keywords 4.4 Bpub Perl Data Structure 4.5 Defining Keywords 4.6 Bpub Keyword Methods 5. Final Words _______________________ 1. Introduction I usually call bpublish an automatic HTML generator, but it's really much more than that. It allows you to design your own content description language, specifically tailored to the type of content you need to describe, and provides a framework for writing your own output generator, for whatever sort of output you need to generate. You write up your content with the flexible bpub syntax and user-defined keywords, using whatever hierarchical organization is best suited for it. You then use Perl code to describe what each keyword means. bpublish ties everything together, and provides your Perl code with everything it needs to do the job easily. Hand-writing HTML for a complex website constantly involves tedious and error-prone duplication of common constructs and themes. Graphical editors give you little control over the low-level details. Both make you specify WHAT you want, instead of letting you describe HOW to make what you want. bpublish gives you the "how" as well as the "what". The "what" goes in your content description, and the "how" in your Perl code. You get complete control over the details, but it eliminates the tedium by turning website design into a programming problem. You don't write the HTML -- you write Perl code to write HTML for you. If you design your architecture right, you can end up with complete localization of the description -- each bit of information defined in just one place. That means that you can make any change to your output, no matter how global in scope, by editing a single section of a single file. Your change can then propagate to one, or ten, or a hundred output files in a matter of seconds. 2. Installation 1. Untar the archive, and go into the bpublish/ directory. 2. Change the first line of bpublish.pl to point to wherever Perl is installed on your system. (Type "which perl" if you don't know). 3. Type "ln -s bpublish.pl bpub". 4. That's all. Isn't Perl wonderful? 3. User's Automatic Most people don't want to read the manual. They'd rather get up and running as quick as possible. This section attempts to explain bpub with as little prose and as many examples as possible. The examples here actually cover almost everything you really need to know. If you aren't concerned with formality, you can skip most of Section 4 for now. 3.1 The Basics A bpub file is a block of keywords. Each keyword may have various properties. The values of these properties are themselves blocks of keywords. And so on. When bpublish parses a bpub file, it converts each keyword to a Perl object (hash). The hash keys are the property names, and the values are references to blocks (arrays). Each member of the array is a reference to a keyword object (hash). And so on. When bpublish evaluates this file, it asks the keyword objects in the top-level block for their text output. The keyword objects will usually ask their properties for their text output, and this continues down the hierarchy until all the keywords have had their say. The information propagates back up the hierarchy, and eventually the entire output file is generated. Some keywords are built in, but most are user-defined. User keywords are defined by writing a perl file with a name of the form "bpk_*.pl". This is called a bpk file. A couple of example files follow. For more examples, browse through the examples directory provided with the bpublish distribution. 3.2 Example bpub File hamlet ( /* the keyword is "hamlet" */ fee='To be, or not to be' /* the "fee" property is set to a literal string */ fie="that is the /* double-quotes compress consecutive question" whitespace into a single space */ foe=nobility(place="mind") /* the "foe" property is set to a block containing the "nobility" keyword */ fum={ slings() arrows() } /* "fum" is set to a block containing the "slings" and "arrows" keywords */ foo /* the "foo" property is set to an empty block */ whine=[ The time is out of joint: O cursed spite, That ever I was born to set it right! ] /* square brackets quote and preserve formatting */ ) /* The following three lines are equivalent. Note the curly braces (not parenthesis) in the last two lines. */ polonius ( content={ "To thine own self be " randomboolean() "..." } ) polonius { "To thine own self be " randomboolean() "..." } polonius { "To thine own self be ${randomboolean()}..." } file (filename="nunnery.html" content=include{"ophelia.bpub"} ) /* reads and evaluates the file "ophelia.bpub", and writes the result to the file "nunnery.html" */ 3.3 Example bpk File # bpk_shakespeare.pl # # Hamlet: keywords are normally documented at the top of # the file like this, with each of the properties listed. # fee: a required property, must be specified in the keyword # fie (inheritable): a property that can be specified in an # ancestor keyword # foe (optional): a property that doesn't necessarily have to # specified at all # fum (optional inheritable): a property that can be specified # in an ancestor keyword, or not at all # foo (boolean): only the existence of this property matters, # not its value package Hamlet; # declare a package with keyword name capitalized @ISA = ("Keyword"); # inherit bpub's keyword methods sub gettext { # gettext is called when the keyword is evaluated my ($self) = @_; # the only argument is the keyword object my $fee = $self->getpropertytext("fee"); # required property my $fie = $self->inheritpropertytext("fie"); # inheritable property my $foe = $self->trypropertytext("foo"); # optional property my $fum = $self->tryinheritpropertytext("fum"); # optional inheritable property my $foo = $$self{foo}; # boolean property my ($flesh, $dew) = $self->getpropertytext(qw(flesh dew)); # you can get more than one property at once foreach (keys %$self) { next if /^_/; print "The $_ property is ", $self->getpropertytext($_), "\n"; # iterate over each property. (Properties that begin with # an underscore are special.) } if ($$self{fum}) { foreach (@{$$self{fum}}) { print "The fum property's block contains a ", ref $_, "\n"; # iterate over each keyword in a property's block } } return "The rest is silence."; # the return value should be a string representing the result # of the keyword evaluation. } 1; # end the bpk file with a 1, so the file evaluates true 4. User's Manual bpublish's basic behavior is: - you run it - it imports any bpk files you may have provided - it reads and parses the bpub file you specify - it creates a data structure in memory to represent the file - it evaluates the file by asking the objects in the data structure to return a text representation - it outputs the results The following sections will explain these processes in detail. 4.1 Command Line Syntax Provided you've set up the symlink as suggested in the installation instructions, you can rub bpublish just like one would expect: bpub [options] [filename(s)] The bpub file(s) given are read, parsed, and evaluated. If no filenames are given and the -e option is not used, bpub reads from standard input. The output is written to standard output. (Note that in a lot of cases, there is no direct output because you've trapped it all with "file" keywords.) Here are the command line options you can give: -i INPATH sets the default directory for reading bpub files. If not given, the current directory is used. -o OUTPATH sets the default directory for writing output files. If not given, the current directory is used. -e EVALSTRING parses and evaluates the given string as if it were a small bpub file. Multiple -e options can be given, just like multiple filenames can be specified. -q quiet mode. No status messages are printed to stderr. -m low memory mode. The line and character positions are not stored with the keywords, and image info is not cached. This saves a bit of memory, but makes error messages ambiguous. -v verify mode. The bpub files are parsed and evaluated, but no output files are written. -d debug mode. The bpub files are parsed and a picture of the data structure is drawn. Not generally useful. 4.2 Bpub Syntax In bpub, everything boils down to blocks of keywords. Block/Keyword Syntax: Blocks are normally delimited with { curly braces }. Inside those braces are zero or more keywords. Each keyword is followed by a collection of zero or more properties in ( parentheses ). { } /* empty block */ { keyword() } /* block with one keyword, no props */ { key() word(property) } /* block with two keywords. Second keyword has a property. */ Properties are normally given by the property name, followed by an equals sign and a block. keyword( property={...} anotherproperty={...} ) The keywords in the property's blocks are said to be children of this keyword, and this keyword is their parent. If a keyword appears where a block is expected, it will be placed in a block by itself. Thus, keyword( property=foo() ) can be used for: keyword( property={ foo() } ) If a keyword is followed by curly braces instead of parentheses, the block is assigned to the keyword's "content" property. Thus, keyword{ some() block() } can be used for: keyword( content={ some() block() } ) If a property name is not followed by an equals sign, it is assigned an empty block. Thus, keyword( property ) can be used for: keyword( property={} ) This is frequently used for specifying "boolean" properties, where only the presence of the property matters, not its value. You can { nest { curly { braces } } }, but it will all end up being flattened into a single block. You can't have blocks within blocks, just blocks within keyword properties within blocks. /* Comments are given C-style. */ Literal String Syntax: If 'text in single-quotes' appears where a keyword is expected, bpub will generate a special String keyword which will return the text when its gettext method is called. All newlines are translated to spaces. However, you are allowed to use escape characters such as \n -- they will be translated to whatever Perl thinks they should be. Likewise, can embed single quotes into the string by preceeding them with backslashes. "Text in double-quotes" is the same, except one or more whitespace characters in a row will be replaced with a single space. [Text in square brackets] is called "block-quoted", and is normally used for multi-line, preformatted text. If the first newline is preceded only by whitespace, it and the space are removed. If the last newline is followed only by whitespace, the space (but not the newline) is removed. (This provides some leniency in bracket placement.) All other newlines and spaces are left intact. If the text itself contains square brackets, they should be either [ matched ] or \] escaped \[. Variable and Block Interpolation: If ${someword} appears in a string, it will be replaced with the value of the "someword" bpub variable, assuming you have defined that variable before the string is evaluated. 'Hamlet in madness hath ${deadguy} slain.' is exactly equivalent to: { 'Hamlet in madness hath ' var{"deadguy"} ' slain.' } If ${block} appears in your string, it will be replaced with the contents of the block. 'They bore him barefaced on the bier, ${nonny(count="4")}...' is exactly equivalent to: { 'They bore him barefaced on the bier, ' nonny(count="4") '...' } Notice that an embedded block can itself contain strings, which in turn can contain their own embedded blocks, and so on. The bpub parser won't get confused, but you might. Note that the ${...} interpolative construct can be used to switch from a keyword-centric model (strings embedded in bpub code) to a more text-centric model (bpub code embedded in text), which is frequently useful. At the extreme, you can put square brackets around your entire file and pretend you are using a php/asp-like embedded scripting language. 4.3 Bpub Core Keywords A number of built-in keywords are provided for basic stuff. Each keyword here is followed by a list of its properties. Remember that if a keyword only takes the single property "content", it can be invoked{"like this"}. General Purpose Core Keywords: include: this actually isn't a real keyword (it's more like a compiler directive), but it looks like one. At parse time, it reads in another bpub file, and inserts the resulting block in place of the include keyword. At evaluate time, include no longer exists. This is not the same as: bpub{read{"foo.bpub"}} which parses and evaluates foo.bpub at evaluate time. content: filename to read. Unqualified filenames are taken to be relative to the inpath. Note that this property is -evaluated- at parse time, before the main file has started evaluating. file: creates a file and writes text to it filename: name of the file to be written. Unqualified filenames are taken to be relative to the outpath. content: the data to be written to the file. read: reads a text file, and returns it as a string. content: filename to be read. Unqualified filenames are looked for first in the current directory, then in the inpath. hide: ignores all properties and returns nothing. Useful for "commenting out" sections using matched braces. swallow: evaluates all properties, but returns nothing. Useful if you want the side-effect of a keyword evaluation (such as the generation of a file) but don't want the return value. time: returns the current time, formatted nicely content (optional): If "24", time is given in 24-hour time, hh:mm. If "12", time is given as hh:mm, no am/pm. If not given, time is given as hh:mm am/pm. seconds (optional): If given, returns the time from that many seconds since the epoch. If not given, uses the current time. date: returns the current date, formatted however you like content (optional): Template to fill with date info. Strings of the following characters are replaced as so: w: full name of weekday W: three letter abbreviation for weekday n: full name of month N: three letter abbreviation for month m: month number, no longer than the character string M: month number with leading zeroes, exactly as long as the character string d/D: same thing with day number y/Y: same thing with year If not given, default is "mm/dd/yy". seconds (optional): If given, returns the date from that many seconds since the epoch. If not given, uses the current date. Examples: date() returns 4/8/02 date{"w, mm/dd/yy"} returns Monday, 4/8/02 date{"W N DD YYYY"} returns Mon Apr 08 2002 date{"n dd is a w."} returns April 8 is a Monday. Core Keywords For Persistent Data: bpublish provides Storage, a simple but useful model for keeping persistent data around. Think of it as a poor man's database. (More like a destitute man, really, but it gets the job done.) Data is stored in automatically generated bpub files in your inpath. Storage files are only read into memory when they are first needed, and are only written to disk when the program exits and if they've been modified, so it's all fairly efficient. When you need to access Storage from within your bpk file, simply use the getcreatetext keyword method. If you want to change the default storage filename, you can set $Storage::defaultfile in your bpk_setup.pl file. store: stores key/value pairs into a storage file. If the file doesn't exist, it is created. file (optional): storage filename to use. If not given, it defaults to $Storage::defaultfile, which in turn defaults to "_storage.bpub". All other properties are the keys to be stored. retrieve: retrieves a value from a storage file. If the key doesn't exist, "" is returned. content: the key to retrieve on file (optional): storage filename to use. See above. unstore: deletes key/value pairs from a storage file. file (optional): storage filename to use. See above. All other property names are the keys to be deleted. Examples: # in a keyword method in a bpk file: $self->getcreatetext("Store", poloniuslocation => "supper" hamletlocation => "England" ); /* in a bpub file: */ funnyjoke{[ Claudius: Now Hamlet, where's Polonius? Hamlet: At ${ retrieve{"poloniuslocation"} }. ]} # in a shell script? setenv HAMLET `bpub -e 'retrieve{"hamletlocation"}'` Core Keywords For Dynamic Evaluation: bpub: parses a string as a block with the bpub interpreter, evaluates it, and returns the result. The bpub keyword is the parent of the keywords in the evaluated block. content: text to evaluate as a bpub file perl: evaluates a string with the perl interpreter and returns the result. Useful for math operations and comparisons. content: text to evaluate as a perl expression early: this is another parser directive in the guise of a keyword. The content property's block is evaluated at parse time. The result of the evaluation is turned into a "string" keyword, which replaces the "early" keyword. For example, early{perl{ [ package Foo; ...etc... ]}} could be used to dynamically define a new keyword within the bpub file. You really shouldn't do that sort of thing too often. content: block to be evaluated at parse time Core Keywords For Bpub Variables: makevar: creates bpub variables. The scope of a variable is similar to the way "local" works in Perl. The variable is visible to the block it was created in and all subblocks. If a subblock uses makevar again to create a variable with the same name, it gets its own copy of the variable to play with, which is restored to the original value when the subblock ends. Returns nothing. Each property name is a variable to be created, and the property's value, if any, is the initial value for the variable. setvar: assigns a value to a bpub variable. Returns nothing. Each property name is a variable to be assigned to, and the property's value is the value to assign. var: returns the value of a variable. (Remember that within strings, ${varname} can be used instead of the var keyword.) content: name of the variable macro: fills in a template using its properties. Macro parameters are given in the template in square brackets. For example, macro(content="the [noun]'s the thing" noun="play") returns "the play's the thing". content (optional): the template name (optional): name of variable to use as template Either content or name is required. name="fardle" is equivalent to content=var{"fardle"} other properties as named in the template Core Keywords For Control Structures: The use of control structures within the bpub file is discouraged, since most programming-like behavior should be confined to the bpk file. But if you need them, some simple conditional and iterative constructs are provided. repeat: evaluates the content multiple times and returns the concatenated result count: number of repetitions content: the block to be repeated while: evaluates the content while a condition evaluates to true, and returns the concatenated result condition: follows perl's truth rules (zero or blank string is false, most else is true) content: the block to be repeated if: evaluates the condition, and then evaluates and returns either the "then" or "else" property condition: follows perl's truth rules (zero or blank string is false, most else is true) then (optional): returned if condition is true, or "" if not given else (optional): returned if condition is false, or "" if not given Example: makevar(message="I've printed this [num] time[plural].\n" count="1") while (condition=perl{ "${count} <= 4" } content={ macro(name="message" num=var{"count"} plural=if(condition=perl{ "${count} != 1"} then="s")) setvar(count=perl{ "${count}+1" }) }) prints out this: I've printed this 1 time. I've printed this 2 times. I've printed this 3 times. I've printed this 4 times. 4.4 Bpub Perl Data Structure As the parser goes through the bpub file, it creates a data structure in memory. The evaluator then gets to work on this data structure, and that's when your keyword methods get called. Your Perl code can examine this data structure and return the appropriate text. Each of the bpub concepts has a direct analog within the Perl code. A "block of keywords" is represented with an anonymous array of anonymous hash references. A keyword is represented with a hash that has been blessed with the keyword's name, i.e. a Perl object. The keyword's property names are the keys in the hash, and the keyword's property's blocks are represented by the values in the hash, which are array references. For example, the data structure for this bpub description: world ( uses={ weary() stale() flat() } garden="unweeded" ) frailty (name="woman") is similar to that generated by this Perl code: $blockref = [ { uses => [ { }, { }, { } ], garden => [ { _content => unweeded } ] }, { name => [ { _content => woman } ] } ] except the blessing of the hashes (and thus the keyword names) are not shown. Notice that every array member is a hash reference, and the value of every hashkey that doesn't begin with "_" is an array reference. In general, values of hashkeys that don't begin with an underscore are always array references. Values of hashkeys that begin with a single underscore should be strings (they can be and usually are generated by the user rather than the parser). Values of hashkeys that begin with a double underscore are special scalars that should only be generated by the parser: __parent: reference to the keyword's parent keyword, or undef if the keyword is in the top-level block. __filename: name of the bpub file which generated this keyword. (To get it, use inheritpropertytext(), not getpropertytext().) Don't expect it to necessarily be a valid filename. __linenum: line number within the bpub file at which the keyword was generated. __charnum: character number within the line at which the keyword was generated. 4.5 Defining Keywords In order to breathe life in your bpub keywords, you must create a bpk file. This is simply a perl script with a name of the form "bpk_*.pl". bpk files can be placed either in the inpath or in the same directory as the bpublish executable. bpublish will automatically look for and use any bpk files in these directories. (Since bpk files are "require"d near the beginning of the program, a bpk file can also be used for "run commands". For example, I have one that looks like this: # bpk_setup.pl: run commands $main::outpath = "/home/bret/www/data/"; This avoids having to specify the -o command line option every time.) To define a keyword in the bpk file, declare a package with the name of the keyword. The package name must be capitalized. Set the package's @ISA so it can inherit from the Keyword package. If you are using prefixes, and this keyword contributes to the prefix, set the package's $prefixproperty variable to the appropriate property name. Now, define a gettext() method. This method is called when the keyword is evaluated, and should return a text string that represents the result of the evaluation. It is called as an object method, so the first (and only) argument is a reference to the keyword object currently being evaluated. You may define as many keywords as you like in a single file. Make sure to end the file with: 1; so that Perl's "require" command accepts it. See Section 3.3 for some fairly complete skeleton code. 4.6 Bpub Keyword Methods bpublish provides a nice set of functions to help you crawl around the data structure and get the information you need. These are part of the Keyword package, which your keyword should inherit from. They should be invoked like so: $self->keywordfunction() where $self is a reference to your keyword object. Getting Property Blocks: $self->getproperty($propertyname) or (@listofpropertynames) $self->getproperties($propertyname) or (@listofpropertynames) These two functions are synonymous. If called in a scalar context, they return the array reference for the first (and presumably only) property name given. If called in a list context, they return a list of property array references. If a property doesn't exist, a descriptive error message is printed and the program exits. These functions are not unlike simply saying $$self{"propertyname"}, but they provide the appropriate error-handling. $self->tryproperty($propertyname) or (@listofpropertynames) $self->tryproperties($propertyname) or (@listofpropertynames) Behave exactly the same as getproperties, except that if a property isn't found, undef is returned in its place instead of triggering an error. $self->inheritproperty($propertyname) or (@listofpropertynames) $self->inheritproperties($propertyname) or (@listofpropertynames) Behave exactly the same as getproperties, except that if a property isn't found, they go up through the __parent chain looking for it. If it's still not found, an error message is printed and the program exits. $self->tryinheritproperty($propertyname) or (@listofpropertynames) $self->tryinheritproperties($propertyname) or (@listofpropertynames) Behave exactly the same as inheritproperties, except that if a property cannot be found, undef is returned in its place instead of triggering an error. Evaluating Properties: $self->getpropertytext($propertyname) or (@listofpropertynames) This is the most common keyword method. It evaluates a property block by calling the gettext method for each member, concatenating and returning the results. Same scalar/list context behavior as above. $self->inheritpropertytext($propertyname) or (@listofpropertynames) Same as getpropertytext, except if a property isn't found, it goes up through the __parent chain looking for it. $self->trypropertytext($propertyname) or (@listofpropertynames) Same as getpropertytext, except if a property isn't found, undef is returned instead of triggering an error. $self->tryinheritpropertytext($propertyname) or (@list) Same as inheritpropertytext, except if a property isn't found, undef is returned instead of triggering an error. Sharing Functionality Among Keywords: It is possible to share functionality in the typical style, letting keywords inherit each other's methods through the @ISA array. However, in bpublish, you typically only define the gettext method for each keyword, so this becomes awkward. The preferred and easiest techniques for invoking another keyword are creating and morphing. $self->create($keywordname, property1 => "value1", property2 => "value2", etc...) $self->create($keywordname, $contentpropertystring) Creates and returns a keyword object of the given type. The object has the given properties, which will return the given values when evaluated. If a property value is given as undef, that property is not used. If a property value begins with an underscore, the value string is attached directly to the property instead of being put in a keyword object in a block. If the property/value list has only one entry, it is taken to be a value for the "content" property. $self->getcreatetext( same arguments as create ) Creates a keyword object just like create, evaluates it by calling its gettext method, and returns the result. This is the function you normally use to "call" a keyword as if it were a subroutine ("has a" relationship). Example: $yesterday = $self->getcreatetext("Date", content => "w, n dd, yyyy", seconds => time - 60*60*24); $self->morph($keywordname, $function, @args) "Casts" $self to the specified keyword, calls the function with the given arguments (if any), casts the object back to its original keyword, and returns the function's result. For example, $self->morph("Fardle", "bear") will pretend that $self is a Fardle, and return the result of Fardle::bear(). $self->getmorphtext($keyword) Equivalent to $self->morph($keyword, "gettext"). This is the function you normally use to "inherit" another keyword's behavior ("is a" relationship). For examples, see below or look at bpk_link.pl in the examples directory. $self->addpropertytext( property1 => "value1", property2 => "value2", etc... ) Attaches new properties to $self that will evaluate to the given value strings. If a value is undef, the property will be set to an empty block (as if you were specifying a boolean property). If a property already exists, it will be replaced. This function is most useful for transforming $self into something that can be morphed. Example: # part of a very simple Htmlfile::gettext() my $content = $self->getpropertytext("content"); my $newcontent = "$content"; $self->addpropertytext( content => $newcontent ); return $self->getmorphtext("File"); Dynamic Parsing, Block Evaluation, File I/O: $self->createfile($filename, $text, $useinpath) Creates a file with the name $filename and writes the text in the string $text to it. If $useinpath is false or not given, the filename is taken relative to the outpath. If $useinpath is true, the filename is relative to the inpath. $self->parsefile($filename) Reads in a bpub file, parses it, and returns a block. $self is the parent of the top-level keywords in the block. $self->parse($bpubcode) Parses a string containing bpub code, and returns a block. $self is the parent of the top-level keywords in the block. $self->getblocktext($block) Evaluates a block by calling the gettext method for each member, concatenating and returning the results as a string. $self->getparsetext($bpubcode) Parses a string containing bpub code, then evaluates the resulting block, and returns a string representing the result of the evaluation. $self is the parent of the top-level keywords in the block. Dealing with Images: Unless the -m command line switch is given, image info is cached by default, so the next time you look up a given image, it returns immediately without inspecting the file. To disable caching, in your bpk_setup.pl file (or wherever) set $Image::caching = 0. $self->tryimageinfo($imagename) $imagename should be the filename of an image file, but without the ".gif", ".jpg", or ".png" suffix. Unqualified filenames are taken relative to the outpath. The function will look for an image with one of those suffixes and if found, return the info in a list: ($suffix, $width, $height) where $suffix will be "gif", "jpg", or "png". If no image is found, it returns ("",0,0). $self->getimageinfo($imagename) Same as tryimageinfo, but exits with an error message if the image is not found. $self->tryimages(@listofimagenames) Calls tryimageinfo for each name in the list, and returns the result as a list of hash references. Each hash has the keys "name", "suffix", "width", and "height". If an image is not found, undef instead of a hash reference will be returned at its spot in the list. $self->getimages(@listofimagenames) Same as tryimages, but exits with an error message if any image is not found. Dealing with Prefixes: $self->getprefix() Makes a prefix string by going up the __parent chain. If a non-$self object is found with the package variable $prefixproperty defined, then the (inheritable) value of the property by that name is added to the prefix at the front, unless the value is "". If any object is found with a "prefix" property defined, that value is added to the prefix and the process stops. Otherwise it continues until it hits the top-level block. Returns a list of two strings, one using underscores as separators and one using slashes, both with trailing separators. For example: lament (word="alas" content={ character (name="yorrick" status="poor" back={ borne ( times="1000" who={ character (name="hamlet") }) }) }) If we have: $Lament::prefixproperty = "word" $Character::prefixproperty = "name" Then for the innermost object above, getprefix() would return: ( "alas_yorrick_", "alas/yorrick/" ) But if status="poor" is replaced with prefix="king/jester": ( "king_jester_yorrick_", "king/jester/yorrick/" ) $self->getuprefix() Same as getprefix, but only returns the prefix string that uses underscores as separators. $self->getsprefix() Same as getprefix, but only returns the prefix string that uses slashes as separators. Dealing with Subproperties: Subproperties are properties of the keywords within a particular property block. For example, with something like this: folks ( content={ character ( name="hamlet" role="prince" ) character ( name="polonius" role="fishmonger" ) character ( name="ophelia" role="hottie" ) }) The subproperties of the "folks" keyword are "name" and "role". $self->attachsubpropertytext($contentpropertyname, @listofsubpropertynames) For each keyword in the content property's block, for each named subproperty, creates a new entry in the keyword's hash with the key equal to the subproperty name prefixed with an underscore, and the value equal to the result of getpropertytext called on that subproperty. For the example above, $self->attachsubpropertytext("content", "name", "role"); would have the effect of: $self->{"content"}->[0]->{"_name"} = "hamlet"; $self->{"content"}->[0]->{"_role"} = "prince"; $self->{"content"}->[1]->{"_name"} = "polonius"; $self->{"content"}->[1]->{"_role"} = "fishmonger"; $self->{"content"}->[2]->{"_name"} = "ophelia"; $self->{"content"}->[2]->{"_role"} = "hottie"; Remember that hash values for keys that don't begin with an underscore are always block references. Only keys that begin with an underscore can have values that are scalar strings like these. $self->getsubpropertytextlist($contentpropertyname, @listofsubpropertynames) Returns a list of list references, one for each subproperty named. Each reference points to a list of strings, one for each member of the content block. Note that this ordering is opposite of getpropertytexthash, below. For the example above, $self->getsubpropertytextlist("content", "name", "role"); would return: ( [ "hamlet", "polonius", "ophelia" ], [ "prince", "fishmonger", "hottie" ] ) $self->getsubpropertytexthash($contentpropertyname, @listofsubpropertynames) Returns a list of hash references, one for each member of the content block. Each reference points to a hash with keys corresponding to the requested subproperties and values of text strings. For the example above, $self->getsubpropertytexthash("content", "name", "role"); would return: ( { name => hamlet, role => prince }, { name => polonius, role => fishmonger }, { name => ophelia, role => hottie } ) Error Handling: $self->notify($messagestring) Prints the message to stderr unless quiet mode is on. If the message does not end with a newline, keyword, filename, and line/char info are appended. $self->fatal($errormessage) Prints the error message to stderr and exits the program. If the message does not end with a newline, keyword, filename, and line/char info are appended. 5. Final Words This program is FREEWARE for non-commercial use. I wrote it to help me with my personal website, but I ended up liking it so much that I improved it to the point where it was publicly releasable. And here it is. Try it out, use it, enjoy it! It has made my life much easier; hopefully it can help you as well. It is my expectation that the learning curve for this program should be rather short, provided that you already know Perl. The bpub syntax is pretty straightforward, and the rest of it, you write yourself! bpublish just fills in the gap between your data and your Perl code. If you do end up using bpublish for anything, I would love to hear from you. Please send any comments, questions, suggestions, bug reports, or just idle chatter to: bret@ugcs.caltech.edu Also, check my website periodically for updates: http://www.ugcs.caltech.edu/~bret _______________________ copyright (c) 2002 Bret Victor (exeunt)