4
$\begingroup$

How might I create a function that converts between FullForm and a XML structuce?

Ultimately I'm ideally looking to use CSS and JS to edited the XML structure.

For example using jquery I might do something like $('Times Power').

Ideally I'm looking for the following code to export the following xml.

Times[a, Rational[1, -2], "test"]

to

<?xml version="1.0" encoding="UTF-8"?>
<Times>
  <Symbol>a</Symbol>
  <Rational>
   <Integer>1</Integer>
   <Integer>-2</Integer>
  </Rational>
  <String>test</String>
</Times>
$\endgroup$
  • $\begingroup$ Would TreeForm be somewhat in the ballpark? Consider also that TreeForm takes the same options as TreePlot, so it may be reasonably customizable. $\endgroup$ – MarcoB Sep 3 '15 at 21:02
  • $\begingroup$ @MarcoB I'm not exactly sure what you mean. If you don't think this is a good idea feel free to downvote. I'm most interested in trying to use Unix tools after the conversion has taken place. $\endgroup$ – William Sep 3 '15 at 21:05
  • $\begingroup$ I don't have anything against the idea. I guess I could use some help in understanding what you are trying to achieve specifically after producing the structure. Honestly it doesn't help that I can't think of any reasonable meaning for your sample input ("test") / z. Maybe you could expand on what you want to do with this "folder structure form". $\endgroup$ – MarcoB Sep 3 '15 at 21:08
  • $\begingroup$ @MarcoB I would like to try and use Unix tools to mv files around. Do you understand the example above and how it relates to Times["test", Power[z, -1]]? I can''t tell if you misunderstand the example or just don't get the intent of converting it to a directory structure. $\endgroup$ – William Sep 3 '15 at 21:13
  • 3
    $\begingroup$ Are you aware of MathML? It uses different tags but in its content variant its structure comes close to what you want. AND it is an official standard, so there are chances that you'll find information and libraries which help you to do what you want to do with CSS and JS. Mathematica can export to (content) MathML: Export[expr,"MathML","Content" -> True, "Presentation" -> False]. $\endgroup$ – Albert Retey Sep 4 '15 at 7:25
4
$\begingroup$

I think I can understand this latter version of the question better. Unfortunately though I don't speak jquery, so I don't really understand your example, therefore I will leave the applications to you, and limit this answer to getting an XML form of a Mathematica expression.

As it happens, achieving something along those lines is pretty simple, since Export handles XML natively.

For instance, let's have a very simple expression such as x / y, whose FullForm nevertheless is different from its InputForm, since you are concentrating on FullForms in your question:

FullForm[x/y]
(* Out: Times[x,Power[y,-1]] *)

We can generate an XML representation of this expression as follows:

xmlstring = ExportString[x/y, "XML"]

(* Out: 
<?xml version='1.0'?>
<!DOCTYPE Expression SYSTEM 'http://www.wolfram.com/XML/notebookml1.dtd'>
<Expression xmlns:mathematica='http://www.wolfram.com/XML/'
    xmlns='http://www.wolfram.com/XML/'>
  <Function>
    <Symbol>Times</Symbol>
    <Symbol>x</Symbol>
    <Function>
      <Symbol>Power</Symbol>
      <Symbol>y</Symbol>
      <Number>-1</Number>
    </Function>
  </Function>
</Expression>
*)

You will notice that this is already an explicit representation of the FullForm of the input expression.


If you would like to carry out further modifications to the XML structure in Mathematica, you can also import the structure as XMLObjects:

ImportString[xmlstring, "XML"]

XMLObject["Document"][{XMLObject["Declaration"]["Version" -> "1.0"], XMLObject["Doctype"]["Expression", "System" -> "http://www.wolfram.com/XML/notebookml1.dtd"]}, XMLElement[ "Expression", {{"http://www.w3.org/2000/xmlns/", "mathematica"} -> "http://www.wolfram.com/XML/", {"http://www.w3.org/2000/xmlns/", "xmlns"} -> "http://www.wolfram.com/XML/"}, {XMLElement[ "Function", {}, {XMLElement["Symbol", {}, {"Times"}], XMLElement["Symbol", {}, {"x"}], XMLElement[ "Function", {}, {XMLElement["Symbol", {}, {"Power"}], XMLElement["Symbol", {}, {"y"}], XMLElement["Number", {}, {"-1"}]}]}]}], {}, "Valid" -> True]


If you'd like to have a Mathematica expression back from the XML version, you can also do that by importing it as "ExpressionML" format:

ImportString[xmlstring, "ExpressionML"]
(* Out: x/y *)
$\endgroup$
  • 1
    $\begingroup$ +1 but I'm thinking more of the form <Times>x<Power>y</Power><Power>1</Power></Times>. The current form would be terrible to interact with jQuery. $\endgroup$ – William Sep 4 '15 at 0:33
  • $\begingroup$ hmm.. Wouldn't jQuery be able to handle well-formed XML? It would seem that jQuery.parseXML() might be able to help with that, at least from my reading of its specs. $\endgroup$ – MarcoB Sep 4 '15 at 0:53
  • 1
    $\begingroup$ Yes but that isn't very well formatted IMO. It's a good answer but not what I'm looking for. $\endgroup$ – William Sep 4 '15 at 0:56
  • $\begingroup$ Css Selectors are the problem Symbol:contains('Times') works for a selector with one depth but doesn't work if their nested like you have above. <Number>-1</Number> isn't bad though because technically there really isn't a much better way to do it. $\endgroup$ – William Sep 4 '15 at 6:52
3
$\begingroup$

Combining Michael E2's answer to another question, that redefines XMLElement, and MarcoB's answer above, here is another approach.

To get the Wolfram expression of the messy XML that will recreate the original Wolfram expression (there is probably a better way):

ImportString[ExportString[Hold[Times[a, Rational[1, -2], "test"]], "XML"], "XML"]

This results in:

XMLObject["Document"][{XMLObject["Declaration"]["Version" -> "1.0"], 
  XMLObject["Doctype"]["Expression", 
   "System" -> "http://www.wolfram.com/XML/notebookml1.dtd"]}, 
 XMLElement[
  "Expression", {{"http://www.w3.org/2000/xmlns/", "mathematica"} -> 
    "http://www.wolfram.com/XML/", {"http://www.w3.org/2000/xmlns/", 
     "xmlns"} -> "http://www.wolfram.com/XML/"}, {XMLElement[
    "Function", {}, {XMLElement["Symbol", {}, {"Hold"}], 
     XMLElement[
      "Function", {}, {XMLElement["Symbol", {}, {"Times"}], 
       XMLElement["Symbol", {}, {"a"}], 
       XMLElement[
        "Function", {}, {XMLElement["Symbol", {}, {"Rational"}], 
         XMLElement["Number", {}, {"1"}], 
         XMLElement["Number", {}, {"-2"}]}], 
       XMLElement["String", {}, {"test"}]}]}]}], {}, "Valid" -> True]

Here is a Block that redefines XMLElement to clean up the Wolfram XML, by taking out the Expression, the Hold, and the Functions, and changing Number to Integer. (The XML expression just sits at the bottom - no function call needed):

x1 = Block[{ XMLElement, symbolName},
   (* use block to temporarily redefine XMLElement *)
   symbolName[element : XMLElement["Symbol", attributes_, children_List]] :=
    First[children];
   XMLElement["Expression", attributes_, {child_}] := child;
   XMLElement["Function", attributes_, children_List] :=
    Module[{name},
     name = symbolName[First[children]];
     XMLElement[name, attributes, Rest[children]]];
   XMLElement["Hold", attributes_, {child_}] := child;
   XMLElement["Number", attributes_, {child_}] :=
    Module[{number, name},
     number = Interpreter["Number"][child];
     name = ToString@Head@number;
     XMLElement[name, attributes, child]];
   ImportString[
    ExportString[Hold[Times[a, Rational[1, -2], "test"]], "XML"], 
    "XML"]
   ];

Wolfram complains about bad XML, but still returns:

XMLObject["Document"][{XMLObject["Declaration"]["Version" -> "1.0"], 
  XMLObject["Doctype"]["Expression", 
   "System" -> "http://www.wolfram.com/XML/notebookml1.dtd"]}, 
 XMLElement[
  "Times", {}, {XMLElement["Symbol", {}, {"a"}], 
   XMLElement[
    "Rational", {}, {XMLElement["Integer", {}, {"1"}], 
     XMLElement["Integer", {}, {"-2"}]}], 
   XMLElement["String", {}, {"test"}]}], {}, "Valid" -> False]

To make it readable, here is an indenting print engine:

(* indenting print engine *)
Remove[new, contents, cr, tab, inc, dec, in, open, close, string, 
  indent, initial, pp];
initial = "";
string = initial;
indent = 0;

(* always return the symbol, so can chain *)
pp[new] := Module[{}, string = initial; indent = 0; pp];
pp[contents] := string;
pp[s_String] := Module[{x}, string = string <> s; pp];
pp[cr] := pp["\n"];
pp[tab] := pp["\t"];
(* inc increases indent; dec decreases indent *)
pp[inc] := Module[{}, indent = indent + 1; pp];
pp[dec] := Module[{}, indent = Max[indent - 1, 0]; pp];
(* in makes the indent *)
pp[in] := If[indent > 0, pp[StringRepeat["\t", indent]], pp]

(* add XML to the engine *)
initial = "<?xml version='1.0'?>\n";
pp[open, name_String] := pp["<"][name][">"]
pp[close, name_String] := pp["</"][name][">"]
pp[x : XMLObject["Document"][a_, body_, rest__]] := pp[body];
pp[x : XMLElement[name_String, attributes_, {text_String}]] := 
 Module[{}, pp[open, name][text][close, name]]
pp[x : XMLElement[name_String, attributes_, children_List]] := 
 Module[{},
  pp[open, name][inc];
  Scan[(pp[cr][in][#]) &, children];
  pp[dec][cr][in][close, name]]

To print x1:

pp[new][x1][contents]

Which results in:

"<?xml version='1.0'?>
<Times>
    <Symbol>a</Symbol>
    <Rational>
        <Integer>1</Integer>
        <Integer>-2</Integer>
    </Rational>
    <String>test</String>
</Times>"
$\endgroup$
1
$\begingroup$

This appears to work fairly close.

t = ToBoxes[FullForm@ReleaseHold@ReplaceAll[
       Hold["test"/a], {
        x_String :> String[x],
        x_Real :> Real[x],
        x_Integer :> Integer[x]
        }
       ]][[1, 1]];
ReplaceRepeated[t,
 {
  RowBox[{b_String, "[", x___String, "]"}] :> 
   StringJoin[{"<", b, ">", x, "</", b, ">"}],
  RowBox[{b___String}] :> StringJoin[{b}],
  "," :> ""
  }
 ]

 > "<Times><Power>a<Integer>-1</Integer></Power><String>\"test\"</String>\

"

and converting back.

ReplaceRepeated[
 ImportString[
   "<Times><Power>a<Integer>-1</Integer></Power><String>\"test\"</\
String></Times>", "XML"][[2]], {
  XMLElement["Integer", {}, {x_String}] :> x,
  XMLElement["String", {}, {x_String}] :> x,
  XMLElement[x_String, {}, {y___String}] :> 
   StringJoin[x, "[", StringJoin@Riffle[{y}, ","], "]"]
  }
 ]
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.