12
$\begingroup$

I have a list that I am using for the legend of a plot

gases = {"Air", "He", "Ar", "\!\(\*SubscriptBox[\(N\), \(2\)]\)", "\!\(\*SubscriptBox[\(CO\), \(2\)]\)"};

However, I also want to Export the data as a CSV table. When I do this the header gets all wacky:

ExportString[Join[{gases}, RandomInteger[{0, 1}, {2, 5}]], "CSV"]
"Air,He,Ar,\"N \"
  2,\"CO \"
   2
0,0,0,1,1
1,1,0,1,1"

This is obviously because of the SubscriptBox. So how can I remove these so that

Foo[gases]
    {"Air", "He", "Ar", "N2", "CO2"}
$\endgroup$

5 Answers 5

10
$\begingroup$

A slightly more general version of JasonB's solution:

deSubscript[string_] := StringReplace[string,
  "\!\(\*SubscriptBox[\(" ~~ Shortest[x__] ~~ "\), \(" ~~ 
    Shortest[y__] ~~ "\)]\)" :> x <> y
  ];

which also works if the string contains something other than a single SubscriptBox:

In[243]:= subscriptBoxToString["\!\(\*SubscriptBox[\(aH\), \(2\)]\)\!\
\(\*SubscriptBox[\(123\), \(3\)]\)"]
subscriptBoxToString["\!\(\*SubscriptBox[\(C\), \(2\)]\) \
\!\(\*SubscriptBox[\(NH\), \(3\)]\)"]
subscriptBoxToString["\!\(\*SubscriptBox[\(CO\), \(2\)]\)"]

Out[243]= "aH21233"

Out[244]= "C2 NH3"

Out[245]= "CO2"
$\endgroup$
8
$\begingroup$

This would need to be modified for other forms, but it does what you are asking

deSubscript = 
  StringReplace[#, 
    "\!\(\*SubscriptBox[\(" ~~ var__ ~~ "\), \(" ~~ sub_ ~~ "\)]\)" :>
      var <> sub] &;
gases = {"Air", "He", "Ar", "\!\(\*SubscriptBox[\(N\), \(2\)]\)", 
   "\!\(\*SubscriptBox[\(CO\), \(2\)]\)"};
deSubscript[gases]
(* {"Air", "He", "Ar", "N2", "CO2"} *)
$\endgroup$
2
  • $\begingroup$ Sorry, went with the more general one that glS posted $\endgroup$ Sep 16, 2016 at 20:26
  • $\begingroup$ No apology needed $\endgroup$
    – Jason B.
    Sep 16, 2016 at 20:26
7
$\begingroup$

The most straightforward approach is to convert your string representation of boxes into explicit boxes, and then apply a replacement rule. The internal function which does this was uncovered by John Fultz:

stringToBoxes[s_String] := 
 MathLink`CallFrontEnd[FrontEnd`UndocumentedTestFEParserPacket[s, False]][[1, 1]]

Now

stringToBoxes /@ gases /. SubscriptBox[x_, y_] :> x <> y
{"Air", "He", "Ar", "N2", "CO2"}

Note that this approach won't lead to evaluation leaks if symbols with names Air, He etc. have values because these names are kept as strings during the conversion.

The above solution can be easily generalized for handling more complicated 2D labels, for example:

label = "\!\(\*SubscriptBox[\(N\), \({1,2,3}\)]\)"

output

Replace[stringToBoxes@label, 
 box : _RowBox | _SubscriptBox | _SuperscriptBox :> StringJoin @@ box, {0, Infinity}]
"N{1,2,3}"
$\endgroup$
0
6
$\begingroup$

A string replacement method using the two lines obtained from ToString with mainly StringPosition and StringReplacePart.

ClearAll[toStringWithSubscript];
toStringWithSubscript[str_String] :=
 Module[{lines = StringSplit[ToString[str], "\n"], subPos},
  subPos = 
   StringPosition[Last@lines, Longest[Except[WhitespaceCharacter] ..],
     Overlaps -> False];
  StringReplacePart[
   StringJoin[First@lines, StringRepeat[" ", First@Differences[StringLength /@ lines]]],
   StringTake[Last@lines, subPos],
   subPos]
  ]

ToString on a string with subscripts gives you two lines the second of which contains the substrings. Both lines are padded such that if you used a fixed width font the subscripts would line up under the first line.

Using this information the positions of the subscripts can be collected from the second line using StringPosition and inserted with replacement into the first line using StringReplacePart. We have to pad the end of the first line if the string ended in a subscript as ToString does not pad on the ends.

enter image description here

With a little work this can be extended to consider superscripts and jointly subscripts and superscripts as well.

Hope this helps.

$\endgroup$
6
$\begingroup$
ToString /@ ((Row[{##}] & @@ ToExpression[#, InputForm]) & /@ gases)
 {"Air", "He", "Ar", "N2", "CO2"}

Not safe if Air, CO and other symbols have global values.

And here is a more flexible method, which should not leak evaluation like the former:

 CO=4;He=5;
 Internal`InheritedBlock[{Format,  Subscript, Row}
    ,
    SetAttributes[{Subscript, Row}, HoldAll];
    Format[Subscript[x_, y_]] := Row[{x, y}];

    ToExpression[
       #, StandardForm, Function[s, ToString[Unevaluated@s], HoldFirst]
    ] & /@ gases
 ]
 {"Air", "He", "Ar", "N2", "CO2"}
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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