At 12:40 PM 2000-03-30 -0600, Matt Sisk wrote:
>[...]
>Where I have had the most trouble with the new additions (and breaking
>encapsulation, etc) is in the combination of traverse() based methods
>and my maskable elements. Masking is an ability that seems sort of
>obscure for most any use other than table manipulation. In my table
>trees, when a cell is extended via the colspan or rowspan attr, I
>decided to simply *hide* the cells that this affected rather than
>destroy them -- that way, if the colspan or rowspan is subsequently
>changed I just reveal the element that is already there rather than
>having to remember what it's configuration was before being hidden.
>[...]

I've been thinking a great deal about this lately, because it's such a
problem case and therefore worthy of consideration.  And what I've come up
with is this:  
I consider the semantics of traversal quite straightforward, and so related
to the return value of $element->content_list (and internally,
@{$element->{'_content'} || []} that there is no room for traversal not
seeing some objects that address or content_list can see, or vice versa.
To do otherwise would be simply inconsistent.  So if a node is to be really
masked (i.e., unvisitable by traversal), then it shouldn't be in the tree
-- that is, it shouldn't be in the content list of any element that's in
the tree.
But there's an unending amount of potential space for storing things off
the side of the tree, by having them be in attributes of element in the
tree (i.e., besides _content, _parent, and _tag).

While I'm not familiar with your code (I ran screaming from ElementSuper's
source when I saw the first eval block in it; bad memories of abuses of
nonlocal exiting structures in Lisp), I suggest that the problem lies not
in what you want to do -- address cells in HTML tables -- but in the fact
that you're trying to treat an HTML table as if it were /actually/ a 2D
array, and that the need to mask element-objects falls rather nastily out
of the fact that an HTML table isn't a 2D array, because of rowspans and
colspans.

Now, I'll not advise you on rewriting your code, because you know better
than I really what your code is for and how it does it.  But for the sake
of others on the list, I'll illustrate my point:
One can manage this problem of table cell addressing by storing a map to
the correct elements in an attribute of the table.  I.e., you'd give teach
'table' element a list-of-lists that maps from grid coordinates to what
element occupies/occults that coordinate.  It'd start out:
  $t = HTML::Element->new('table');
  { #make and install a blank map
   my @map = map [undef x $width], 1 .. $height;
   $t->attr('_coordmap', \@map);
  };
then you're free to populate the _coordmap as needed.  If (2,3) were
occulted by having some other cell's colspan or rowspan be >1, this could
be signalled either by having $t->attr('_coordmap')->[2][3] be undef (as
opposed to pointing to the element, which also occurs as a descendant of
$t), or by having it to point to the element that occults that coordinate
(an element that occurs only once in $t->descendants, of course, since no
node can recur in the same tree, but which is free to occur many times in
the _coordmap).

However, while I do think that there is always a better way that doesn't
involve turning a tree into an espalier, I realize that there's More Than
One Way to Do It (altho overriding traverse is never one of them), and I
think that what you do with:

>through bunches of hoops and I have to break encapsulation --
>essentially, what I now have to do is make a masked node "play dead".
>This is accomplished by overriding start_tag() and end_tag() to return
>null values when masked. [...]

...is just fine, and is NOT a breaking of encapsulation.  I don't think
that 'encapsulation' is a vague concept, such that whether or not you're
breaking encapsulation is a terribly subjective thing.  (Altho my judgement
of this as objective is itself subjective, of course.)

If code exists outside of an object's class/suite, and that code directly
reads or writes attributes of the object instead of going thru accessors,
that's breaking encapsulation.  If the documentation to that class says
"but don't use this feature to try to modify object data" (as, for example,
I say in the docs to HTML::Element's content() method), and code outside of
that class/suite does, then that code is breaking encapsulation.  But
overriding a base method in a subclass (which is what you're doing) isn't
breaking encapsulation at all.

So, these are the lessons of this story:

* A node in a tree is a node in a tree, and should be consistently
addressable via the normal structure-related methods.  But if its as_HTML
realization is '', that's fine; it's just that kind of node.
And to that end, I think I'll add a new pseudo-element named "~null" or
something, so that any element whose tag name is "~null" will be
special-cased so starttag and endtag will both return ''.  The element
would still be in the tree, tho, and so would still be visible to
traversal, and in $parent->content_list, etc.  But if you want to make an
existing HTML node invisible, then you'd do something like:
 $it->attr('_real_tag', $it->attr('_tag', '~null'));
and to make it visible again:
 $it->attr('_tag', $it->attr('_real_tag', undef));
(recall that the return value of a PUT call to attr() is the old value; a
handy feature at times!)
Does this ~null idea sound good to everyone?

* Unless your last name is Aas or Burke, you don't get to write code that
says $element->{'stuff'} = $whatever.  Larry once said something about data
privacy in Perl, to the effect that Perl isn't obsessive about enforcing
data privacy because it's a nicer to expect people to stay out of your
living room because they haven't been invited, not because you have a shotgun.
Well, I'm nice, and I don't have a shotgun, but my feral pet ocelot Fang
does sleep under the couch, and he does have a taste for human blood now,
and by the door there's the loose floorboard you could trip over and break
your neck (to say nothing of that dang nest of scorpions that live under
that floorboard), and I did lock and bolt the front door, and I've put up a
"KEEP OUT" sign now.  So take a hint.

* But overriding methods is generally fine.  Of course, sanity would
require that you not override a method with, say:
  sub content_list { randomly_shuffle shift->SUPER::content_list() }
or something that deliberately contradicts the superclass's method's
documented behavior.  That would be basically nutty, but not a violation of
encapsulation.  If you worried that your overriding of starttag et all is
nutty (and nuttiness /is/ a bit subjective), don't worry, it's quite alright.

In fact, a careful reading of the new (1.53) HTML::Element's source will
reveal my experimental and as-yet undocumented attempts to allow for
comments, directives, and other fun in a tree.  The way I'm doing this (or
was in the middle of doing this when I emitted the new Element) is to
modify starttag and endtag (not yet there!) so that a comment doen't look
like <~comment>...</~comment> but instead like <!-- ... -->.  As this is
still a work in progress; since it's undocumented (like the ~null idea I
just outlined above), it's not gospel yet; but the basic idea (modding
starttag and endtag -- and not traverse!) is solid.

--
Sean M. Burke [EMAIL PROTECTED] http://www.netadventure.net/~sburke/

Reply via email to