Subject: Re: [SWIPL] XML parser (again)

From: "Richard A. O'Keefe" <[email-masked]>
Date: Wed, 14 Mar 2001 17:37:52 +1300 (NZDT)

Alberto Ruiz Cristina <[email-masked]> clarified his question.

    Consider this DTD:

	<!ELEMENT example (alberto,other)>
	<!ELEMENT alberto (#PCDATA)>
	<!ELEMENT other (alberto)>

    And consider also this XML document:

	<!DOCTYPE example ...>
	<example>
	 <alberto>Content 1</alberto>
	 <other><alberto>Content 2</alberto></other>
	</example>

    I want to develop a [program in Prolog] based on the Swi Xml Package,
    so that entering the DTD, the XML document and an element identifier,
    it return[s] the desired element from the Prolog structure.

    What I don't know (and I'm realising that this is more a XML
    question than a Prolog question) is how to identify elements, I mean,
    how will the parser know if I want Content 1 or Content 2 if I enter the
    element name "alberto".

So how would you identify a particular element *WITHOUT REFERENCE TO
PROLOG OR SWI*?

There are three popular notations for referring to (sets of) nodes in
SGML/XML documents.  (HyTime is the granddaddy of them all, but who
uses it?  Presumably some small subset of people who can read the spec.)

SGML and XML have the notion of an ID attribute, an attribute whose value
is a document-wide unique label for that particular node.  Altering the
example,

    <!DOCTYPE example [
      <!ELEMENT example (alberto,other)>
      <!ELEMENT alberto (#PCDATA)>
      <!ATTLIST alberto label ID #IMPLIED>
	  <!-- An alberto element may but need not have an attribute called
	       'label' whose value is an identifier that unquely identifies
	      that particular element. -->
      <!ELEMENT other (alberto)>
    ]>
    <example>
     <alberto label="x">Content 1</alberto>
     <other><alberto label="y">Content 2</alberto></other>
    </example>
    
Then you can refer to a node by its label.  That's what "fragment
identifiers" in URLs are all about.

The second notation is the one used in CSS (Cascading Style Sheets).
You can refer to a node by its unique ID, with or without its element
type:
	alberto#x	#x
	alberto#y	#y
You can also test other attributes [att=val].  You can refer to a node
by its ancestry:

	example>alberto		the first one
	example>other>alberto	the second one

If you had
	<!ELEMENT example (alberto+,other)>
	<!ELEMENT other (alberto+)>
	...
	<example><alberto>A</alberto><alberto>B</alberto>
	 <other><alberto>C</alberto><alberto>D</alberto></other>
	</example>
then
	example>alberto		matches the A and B ones
	example>other>alberto	matches the C and D ones

You can also test what comes before a node.  x:first-child must be
the first child of something, y+x is an x that is preceded by a y.
So with this four-albertos example:

	example>alberto:first-child	is A
	alberto+example>alberto		is B
	other>alberto:first-child	is C
	alberto+other>alberto		is D

(I have *NOT* tested this and may well have made a mistake.)

The third notation is XPath, where we'd have

	/example/alberto[1]		is A, also /1/1
	/example/alberto[2]		is B, also /1/2
	/example/other/alberto[1]	is C, also /1/3/1
	/example/other/alberto[2]	is D, also /1/3/2

You can refer to IDs in XPath:
	alberto[id="x"]		-- from version with ID attributes
	alberto[id="y"]
You can also check the text content of a node:
	alberto[string()="A"]		selects the A node
	alberto[string()="B"]
	alberto[string()="C"]
	alberto[string()="D"]

I actually hate XPath because it is syntactically clumsy, amateurishly
defined (if that isn't too strong a word), inconsistent with other W3C
recommendations, and ... I'm writing an article attacking it, can you tell?

However, XPath *is* a W3C recommendation and it *is* used in things like
XLink and XSLT, so anyone working with XML pretty much has to know it.

An interesting project for someone would be an XPath to Prolog compiler.

But whatever notation one uses, it boils down to specifying the
 - properties
 - content
 - and/or context
or the element you want.  SWI Prolog and sgml2pl as such have nothing to
say about this.  Define

    path([], Node, Node).
    path([Step|Steps], element(_,_,Children), Node) :-
        nth1(Step, Children, Child),
        path(Steps, Child, Node).

Suppose you start with
    example( element(example, [], [element(alberto, [label=x], ['A']),
             element(alberto, [], ['B']), element(other, [],
	     [element(alberto, [label=y], ['C']),
	     element(alberto, [], ['D'])])]) ).

then

  ?- example(_A), path([1], _A, X).
  X = element(alberto, [label=x], ['A']) 

  ?- example(_A), path([2], _A, X).
  X = element(alberto, [], ['B']) 

  ?- example(_A), path([3,1], _A, X).
  X = element(alberto, [label=y], ['C']) 

  ?- example(_A), path([3,2], _A, X).
  X = element(alberto, [], ['D']) 

Or try

    content(element(_,_,[Text]), Text) :- atom(Text).

  ?- example(_A), path(P, _A, X), content(X, 'A').
  P = [1]
  X = element(alberto, [label=x], ['A']) 

  ?- example(_A), path(P, _A, X), content(X, 'D').
  P = [3, 2]
  X = element(alberto, [], ['D']) 

Or try

    spath([], Node, Node).
    spath([Step|Steps], element(_,_,Children), Node) :-
        Node1 = element(Step,_,_),
        member(Node1, Children),
        spath(Steps, Node1, Node).

  ?- example(_A), spath(P, _A, X), X = element(alberto,_,_).

  P = [alberto]
  X = element(alberto, [label=x], ['A']) ;

  P = [alberto]
  X = element(alberto, [], ['B']) ;

  P = [other, alberto]
  X = element(alberto, [label=y], ['C']) ;

  P = [other, alberto]
  X = element(alberto, [], ['D'])

In short, the answer to the question "How do I select an element" is
"How do you *WANT* to select it?"