Family Tree program: key, getting a return value from a ...



Family Tree program: key, getting a return value from a called template, local variables, dynamic named elements, xml to xml

This is two sample xml/xsl application that demonstrates what is called 'pull' processing; that is, it is more procedural and less pattern-matching (more like the ranking soccer program and less like the other featured story soccer program). To achieve this, it is necessary to make use of certain features in xml/xsl.

Both examples involve family information. The first involves a family tree xsl file transforms an xml file holding a set of person elements, each holding name and any number of par for parent elements.

Jeanine

Joseph

Esther

Anne

Joseph

Esther



This is a flexible (inclusive) definition. There can be zero, one, two or more parents. Actually, all the elements except one have exactly two par child nodes. The one exception, with name child holding the string Orphan Annie, has no par child nodes. The xsl file is invoked with a parameter (see the set of notes on parameters and JavaScript) holding the subject (parameter named subj). The xsl determines the parents and the siblings of the subject. (See below for two features added.) A sibling is defined as someone with at least one par node holding the same value. Please keep in mind that this is not the only way to define family relationships. The xml tree structure does not in represent the family tree structure, which would not be possible since child nodes have exactly one parent node.

This application uses the key construct to look up a value. Specifically, a key is defined named 'named'. It matches the person element but uses the name child for the look up. The key is not necessary—it, as with many other things, could have been done differently. The advantage of defining a key comes when it is used many times for large files. The xsl parsers (such as the ones 'inside' IE and Mozilla and the stand-alone SAXON) set up internal indices to process keys efficiently.

The critical feature for this application is a trick to have called templates return a value. Called templates do not return a value! However, if the element is contained inside a variable element, then any output in the called template gets stored in the variable. This variable can be used. In the case here, the string value of the variable is compared to the empty string.

Variables in xsl are not like variables in other programming languages. In particular, they do not vary, in the sense that they can be updated. However, a local variable, local to the containing element, can be re-defined. In this application, local variables are defined within elements.

To determine the siblings of the subject person (the person with name equal to the parameter $subj), each of the subject's parents are compared to each of the parents of each of the other persons in the family file. See the for-each inside a for-each below.

| | |

| | |

| | |

| |key defining how to look |

| |up a person element using |

| |the name child |

| |For the external setting |

| |of the parameter in |

| |JavaScript |

| |The node-set of all par |

| |child elements from the |

| |person element whose name |

| |is $subj (using the key |

| |named named. |

| | |

| | |

| | |

| Siblings | |

| | |

| Here is the family of | |

| | |

| |Iterate over the node-set |

| |of parents |

| A parent is: | |

| |Display parents using |

| |default template |

| | |

|Siblings are: | |

| |Iterate over all person |

| |elements except the one |

| |with name $subj |

| |This will hold anything |

| |output by the called |

| |template |

| |call the template |

| |check_if_sibling |

| |Pass the template the |

| |parameter the current node|

| |person element |

| | |

| | |

| |Convert the $result |

| |variable (holding whatever|

| |was output in the called |

| |template) to a string and |

| |check if it is not equal |

| |to the empty string… |

| |… if somewhat was put into|

| |$result, display (output) |

| |the name of the current |

| |node. This is the name of |

| |the person element that |

| |shared par element values |

| |with the $subj |

| | |

| | |

| | |

| | |

| | |

| |Named template |

| |Designated parameter |

| |Make a variable be the |

| |node-set of par elements |

| |child nodes of the person |

| |passed in using the |

| |parameter $p |

| |Outer most of nested |

| |for-each: this iterates |

| |over the $pparents |

| |Set $thisone … |

| |to be the content of the |

| |current par (one of the |

| |$pparents) |

| | |

| |Inner for-each: comparing |

| |each of $pparents to each |

| |of $subjparents. The |

| |$subjparents variable has |

| |global scope |

| |Check for equality |

| |Output this value. This |

| |will be the contents of a |

| |par element. The actual |

| |value is not used. |

| |Note: if the if test |

| |fails, nothing is output. |

| | |

| | |

| | |

| | |

After doing siblings and parents, I have now added determining grandparents. The code uses the same trick of putting code within a variable definition and so saving the results generated by a named template.

One little matter to take care of is that the list of grandparents is separated by a blank followed by 'and' followed by another blank. The last (trailing) instance is removed using the substring and string-length functions.

The addition to the code was in two places: in the main template, before the tag and then afterwards, in a new named template:

| | |

| |Output from called template |

| |will be placed in this |

| |variable |

| |For each parent… |

| |.. find any parents |

| |…for this one |

| | |

| | |

| | |

| |If something was output, |

| |display |

| Grandparents are | |

|. | |

| | |

The named template, fetch_parents, finds any person with name equal to the parameter value and outputs the value of any par elements that are child elements of this person.

| | |

| |Passed parameter will be the name to look for |

| |For each person element in the xml file |

| |Check if its name element equals the passed in value |

| |If it does, consider each par element |

| |output the par value |

| and |along with some text |

| | |

| | |

| | |

| | |

The next (last?) feature to be added is determining cousins. The definition I used for being a cousin is sharing at least one grandparent and not being a sibling. The code checks each person and if the person is not a sibling, applies the test for a cousin. To separate these two features, you would need to re-do the siblings test. Since this test involved determining grandparents, I moved the generation of the subjgrandpars string to outside the definition of the main template.

The cousin check is based on examining two strings: subjgrandpars and pgrandpars. These are created by the same function. The strings each consist of the grandparents with the string ' and ' interspersed in-between the names. The ' and ' is called a delimiter. The fact that there is this delimiter is used in the code. The check is done using recursion in a very common programming technique that works even in the restricted conventions of xsl. The pseudo-code for how it works is the following:

A function called findmatch takes two parameters: alist and blist. It is called initially with subjgrandpars and pgrandpars

findmatch takes off the first name of alist and calls this string first and calls the rest of the string (after the first name and the delimiter) is called rest. The function then checks for first in blist. (Actually, it checks for the concatenation of first and the delimiter. This prevents the string 'Jos' from being a match with the string 'Joseph'. I added two persons to the family to check this. However, it does not prevent the string 'seph' from being a match. Consider this an exercise for the reader!) If there is a match, findmatch returns something. If there is not a match and rest is empty, findmatch returns nothing. If rest is not empty, findmatch calls findmatch with the parameters rest and blist.

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| |Moved to be accessible by|

| |more than one template |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| Siblings | |

| | |

| Here is the family of | |

| | |

| | |

| A parent is: | |

| | |

| | |

|Siblings and cousins: |Heading for siblings and |

| |cousins |

| | |

| | |

| | |

| | |

| | |

| | |

| |Changed from if test |

| | |

| is a sibling. | |

| | |

| |Check if this one is a |

| |cousin |

| | |

| | |

| | |

| | |

| | |

| | |

| is a cousin. | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| Grandparents are | |

|. | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| and | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| |Named template |

| | |

| |Generate (calculate) the |

| |parents of 'p' |

| |Generate the grandparents|

| |of p |

| |For each parent |

| |Call fetch_parents |

| | |

| | |

| | |

| | |

| | |

| |No possibility for being |

| |a cousin if no |

| |grandparents |

| |No possibility if subject|

| |has no grandparents |

| |Otherwise… |

| |Call findmatch with … |

| |pgrandpars as alist |

| |subjgrandpars as blist |

| | |

| | |

| | |

| | |

| |Findmatch named template |

| | |

| | |

| |Extract first |

| |Extract rest |

| | |

| |Check for first being |

| |contained in blist |

| |If it is, output (return |

| |to calling variable) |

| | |

| |If there is a rest (more |

| |names in the alist) |

| |call findmatch |

| |… for alist set to rest |

| |blist |

| | |

| | |

| | |

| | |

| | |

After doing this example, it later occurred to me that it may be good to have family information stored more like a tree. The problem is that xml trees have just one parent for each child. Without being political, the first example allowed any number of parents, including none. I decided to write an xsl file (treeformat1.xsl) that would take the information stored similar to, but not the same as the individual style of the first example (see file familywids.xml) and produce an xml file that arranged the descendants of each parent (see tree.xml). I used the saxon program to produce tree.xml.

For the familywids.xml file, I used id and refid attributes. (Note: if I choose to validate this using a DTD, then the choice of the names 'id' and 'refid' would force checking that the ids were unique and the refids referred to existing id values in certain validating programs. Moreover, parsers such as saxon may make use of the id values in storing the file.)

Here is familywids.xml. Notice that Jeanine and Anne have the same two parents.

Jeanine

Anne

Daniel

Aviva

Ken

Ethyle

Fred

Eduardo

Aledar

Conseula

David

Tom

Debi

Corey

Joshua

Bernardo

Anita

Felez

OrphanAnnie

Funny

Moe

curly

Esther

Joseph

The treeformat1.xsl file is relatively short. The first template determines which person nodes do not have any par (for parent) child notes. We can think of these as ancestors to start of a tree. For each of these, the named template find_descendants is called with 2 parameters: the id for that person (specifically, the id attribute of the name child node) and the value of the name child node. This template (the only other one in the xsl file) checks all the person nodes to check for a par child node with refid attribute value equal to the id passed in as a parameter. Calculating and passing in these two parameters would save time in comparison with re-calculating them for each comparison. The find_descendants template also outputs an element with name the name!

| | |

| | |

| |Produce xml style output |

| | |

| |root node of new file |

| |Loop over each person node |

| |Check if no par subnodes |

| |True: call find_descendants for this|

| |ancestor |

| |Pass the value of the id attribute |

| |and the name |

| | |

| | |

| | |

| | |

| | |

| |Named template |

| | |

| | |

| |Output an element with the name set |

| |to what is the calculated value of |

| |the XPath expression. |

| |Now loop over all person nodes |

| |… checking if each has a par |

| |referring to the id passed in, that |

| |is, a direct descendant |

| |True: call find_descendants of this |

| |node. |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

The xml file produced using familywids.xml and treeformat1.xsl is named tree.xml.

If you make familywids.xml reference treeformat1.xsl and use IE or Mozilla, nothing will be displayed. To produce something that immediately produces HTML, you need to make changes. In what follows, one new feature is the generation of dashes to indicate the generations. This is done using a new parameter and the concat() function. An important conceptual point is that you do not need to strip off the added dashes. The call of the named template handles this for you.

| | |

| | |

| |Change to html output |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| |dashes parameter, initialized to the|

| |empty string |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| | |

| |dashes parameter |

| |Output html p tag |

| |Output current value of dashes |

| |Output as regular text the passed |

| |name |

| | |

| | |

| | |

| | |

| | |

| |add --- for this call |

| | |

| | |

| | |

| | |

| | |

| | |

................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download