Jak programowo iterować przez indeksy dolne, indeks górny i równania znalezione w dokumencie programu Word

12

Mam kilka dokumentów Worda, każdy zawierający kilkaset stron danych naukowych, w tym:

  • Wzory chemiczne (H2SO4 ze wszystkimi odpowiednimi indeksami dolnymi i indeksami górnymi)
  • Liczby naukowe (wykładniki sformatowane przy użyciu indeksu górnego)
  • Wiele równań matematycznych. Napisane przy użyciu edytora równań matematycznych w programie Word.

Problem polega na tym, że przechowywanie tych danych w programie Word nie jest dla nas wydajne. Chcemy więc przechowywać wszystkie te informacje w bazie danych (MySQL). Chcemy przekonwertować formatowanie na LaTex.

Czy jest jakiś sposób na iterację wszystkich skryptów dolnych, indeksów górnych i równań w dokumencie Word za pomocą VBA?

pazury
źródło
Czy zastanawiałeś się nad wyodrębnieniem danych XML z dokumentu, który sam? Wszystkie dokumenty Microsoft 2007+ (.docx) są w zasadzie skompresowanymi plikami XML. Możesz je pobrać za pomocą parsera xml.
James Mertz
opublikowanie go jako komentarza było zbyt długie, dlatego dodałem jako odpowiedź.
James Mertz

Odpowiedzi:

12

Tak jest. Sugerowałbym użycie Powershell, ponieważ całkiem dobrze obsługuje pliki Word. Myślę, że będę najłatwiejszym sposobem.

Więcej na temat automatyzacji Powershell vs Word tutaj: http://www.simple-talk.com/dotnet/.net-tools/com-automation-of-office-applications-via-powershell/

Przekopałem się trochę głębiej i znalazłem ten skrypt PowerShell:

param([string]$docpath,[string]$htmlpath = $docpath)

$srcfiles = Get-ChildItem $docPath -filter "*.doc"
$saveFormat = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveFormat], "wdFormatFilteredHTML");
$word = new-object -comobject word.application
$word.Visible = $False

function saveas-filteredhtml
    {
        $opendoc = $word.documents.open($doc.FullName);
        $opendoc.saveas([ref]"$htmlpath\$doc.fullname.html", [ref]$saveFormat);
        $opendoc.close();
    }

ForEach ($doc in $srcfiles)
    {
        Write-Host "Processing :" $doc.FullName
        saveas-filteredhtml
        $doc = $null
    }

$word.quit();

Zapisz go jako .ps1 i zacznij od:

convertdoc-tohtml.ps1 -docpath "C:\Documents" -htmlpath "C:\Output"

Zapisze cały plik doc z określonego katalogu jako pliki HTML. Mam więc plik doc, w którym mam H2SO4 z indeksami dolnymi, a po konwersji programu PowerShell wyniki są następujące:

<html>

<head>
<meta http-equiv=Content-Type content="text/html; charset=windows-1252">
<meta name=Generator content="Microsoft Word 14 (filtered)">
<style>
<!--
 /* Font Definitions */
 @font-face
    {font-family:Calibri;
    panose-1:2 15 5 2 2 2 4 3 2 4;}
 /* Style Definitions */
 p.MsoNormal, li.MsoNormal, div.MsoNormal
    {margin-top:0in;
    margin-right:0in;
    margin-bottom:10.0pt;
    margin-left:0in;
    line-height:115%;
    font-size:11.0pt;
    font-family:"Calibri","sans-serif";}
.MsoChpDefault
    {font-family:"Calibri","sans-serif";}
.MsoPapDefault
    {margin-bottom:10.0pt;
    line-height:115%;}
@page WordSection1
    {size:8.5in 11.0in;
    margin:1.0in 1.0in 1.0in 1.0in;}
div.WordSection1
    {page:WordSection1;}
-->
</style>

</head>

<body lang=EN-US>

<div class=WordSection1>

<p class=MsoNormal><span lang=PL>H<sub>2</sub>SO<sub>4</sub></span></p>

</div>

</body>

</html>

Jak widać, indeksy dolne mają własne tagi w HTML, więc pozostaje tylko parsowanie pliku w bash lub c ++ w celu wycięcia z body do / body, zmiana na LATEX i usunięcie pozostałych tagów HTML.

Kod z http://blogs.technet.com/b/bshukla/archive/2011/09/27/3347395.aspx


Opracowałem parser w C ++, aby szukać indeksu HTML i zastąpić go indeksem LATEX.

Kod:

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>

using namespace std;

 vector < vector <string> > parse( vector < vector <string> > vec, string filename )
{
        /*
                PARSES SPECIFIED FILE. EACH WORD SEPARATED AND
                PLACED IN VECTOR FIELD.

                REQUIRED INCLUDES:
                                #include <iostream>
                                #include <fstream>
                                #include <string>
                                #include <sstream>
                                #include <vector>

            EXPECTS: TWO DIMENTIONAL VECTOR
                     STRING WITH FILENAME
            RETURNS: TWO DIMENTIONAL VECTOR
                     vec[lines][words]
        */
        string vword;
        ifstream vfile;
        string tmp;

         // FILENAME CONVERSION FROM STING
        //  TO CHAR TABLE

        char cfilename[filename.length()+1];
        if( filename.length() < 126 )
        {
                for(int i = 0; i < filename.length(); i++)
                                cfilename[i] = filename[i];
                cfilename[filename.length()] = '\0';
        }
        else return vec;

         // OPENING FILE
        //
        vfile.open( cfilename );
        if (vfile.is_open())
        {
                while ( vfile.good() )
                {
                        getline( vfile, vword );
                        vector < string > vline;
                        vline.clear();

                        for (int i = 0; i < vword.length(); i++)
                        {
                                tmp = "";
                                 // PARSING CONTENT. OMITTING SPACES AND TABS
                                //
                                while (vword[i] != ' ' && vword[i] != ((char)9) && i < vword.length() )
                                        tmp += vword[i++];
                                if( tmp.length() > 0 ) vline.push_back(tmp);
                        }
                        if (!vline.empty())
                                vec.push_back(vline);
                }
                vfile.close();
        }
        else cout << "Unable to open file " << filename << ".\n";
        return vec;
}

int main()
{
        vector < vector < string > > vec;
        vec = parse( vec, "parse.html" );

        bool body = false;
        for (int i = 0; i < vec.size(); i++)
        {
                for (int j = 0; j < vec[i].size(); j++)
                {
                        if ( vec[i][j] == "<body") body=true;
                        if ( vec[i][j] == "</body>" ) body=false;
                        if ( body == true )
                        {
                                for ( int k=0; k < vec[i][j].size(); k++ )
                                {
                                        if (k+4 < vec[i][j].size() )
                                        {
                                                if (    vec[i][j][k]   == '<' &&
                                                        vec[i][j][k+1] == 's' &&
                                                        vec[i][j][k+2] == 'u' &&
                                                        vec[i][j][k+3] == 'b' &&
                                                        vec[i][j][k+4] == '>' )
                                                {

                                                        string tmp = "";
                                                        while (vec[i][j][k+5] != '<')
                                                        {
                                                                tmp+=vec[i][j][k+5];
                                                                k++;
                                                        }
                                                        tmp = "_{" + tmp + "}";
                                                        k=k+5+5;
                                                        cout << tmp << endl;;
                                                }
                                                else cout << vec[i][j][k];
                                        }
                                        else cout << vec[i][j][k];
                                }
                                cout << endl;
                        }
                }
        }
        return 0;
}

W przypadku pliku HTML:

<html>

<head>
<meta http-equiv=Content-Type content="text/html; charset=windows-1252">
<meta name=Generator content="Microsoft Word 14 (filtered)">
<style>
<!--
 /* Font Definitions */
 @font-face
        {font-family:Calibri;
        panose-1:2 15 5 2 2 2 4 3 2 4;}
 /* Style Definitions */
 p.MsoNormal, li.MsoNormal, div.MsoNormal
        {margin-top:0in;
        margin-right:0in;
        margin-bottom:10.0pt;
        margin-left:0in;
        line-height:115%;
        font-size:11.0pt;
        font-family:"Calibri","sans-serif";}
.MsoChpDefault
        {font-family:"Calibri","sans-serif";}
.MsoPapDefault
        {margin-bottom:10.0pt;
        line-height:115%;}
@page WordSection1
        {size:8.5in 11.0in;
        margin:1.0in 1.0in 1.0in 1.0in;}
div.WordSection1
        {page:WordSection1;}
-->
</style>

</head>

<body lang=EN-US>

<div class=WordSection1>

<p class=MsoNormal><span lang=PL>H<sub>2</sub>SO<sub>4</sub></span></p>

</div>

</body>

</html>

Dane wyjściowe to:

<body
lang=EN-US>
<div
class=WordSection1>
<p
class=MsoNormal><span
lang=PL>H_{2}
SO_{4}
</span></p>
</div>

Oczywiście nie jest to idealne, ale traktowanie jest dowodem koncepcji.

mnmnc
źródło
3

Możesz wyodrębnić xml bezpośrednio z dowolnego dokumentu biurowego w wersji 2007+. Odbywa się to w następujący sposób:

  1. zmień nazwę pliku z .docx na .zip
  2. rozpakuj plik za pomocą 7zip (lub innego programu do rozpakowywania)
  3. Rzeczywistą treść dokumentu należy szukać w wyodrębnionym folderze pod wordpodfolderem i document.xmlplikiem. Powinien zawierać całą treść dokumentu.

wprowadź opis zdjęcia tutaj

Utworzyłem przykładowy dokument i znalazłem go w znacznikach body (zwróć uwagę, że szybko go poskładałem, więc formatowanie może być nieco wyłączone):

<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<w:body>
    -<w:p w:rsidRDefault="000E0C3A" w:rsidR="008B5DAA">
        -<w:r>
            <w:t xml:space="preserve">This </w:t>
        </w:r>
-       <w:r w:rsidRPr="000E0C3A">
            -<w:rPr>
                <w:vertAlign w:val="superscript"/>
            </w:rPr>
            <w:t>is</w:t>
        </w:r>
-       <w:r>
            <w:t xml:space="preserve"> a </w:t>
        </w:r>
            -<w:r w:rsidRPr="000E0C3A">
                -<w:rPr>
                    <w:vertAlign w:val="subscript"/>
                </w:rPr>
                <w:t>test</w:t>
            </w:r>
        -<w:r>
            <w:t>.</w:t>
        </w:r>
    </w:p>
</w:body>

Wygląda na to, że <w:t>znacznik dotyczy tekstu, <w:rPr>jest to definicja czcionki i <w:p>nowy akapit.

Odpowiednik słowa wygląda następująco:

wprowadź opis zdjęcia tutaj

James Mertz
źródło
2

Patrzyłem na inne podejście niż stosowane przez mnmnc.

Moje próby zapisania testowego dokumentu Word jako HTML nie zakończyły się sukcesem. W przeszłości odkryłem, że HTML generowany przez pakiet Office jest tak pełen plew, że wybranie żądanych bitów jest prawie niemożliwe. Przekonałem się, że tak właśnie jest w tym przypadku. Miałem też problem z równaniami. Program Word zapisuje równania jako obrazy. Dla każdego równania będą dwa obrazy, jeden z rozszerzeniem WMZ i jeden z rozszerzeniem GIF. Jeśli wyświetlasz plik HTML w Google Chrome, równania wyglądają OK, ale nie wspaniale; wygląd pasuje do pliku GIF, gdy jest wyświetlany za pomocą narzędzia do wyświetlania / edycji obrazu, które może obsługiwać przezroczyste obrazy. Jeśli wyświetlasz plik HTML w przeglądarce Internet Explorer, równania wyglądają idealnie.

Dodatkowe informacje

Powinienem był zawrzeć tę informację w oryginalnej odpowiedzi.

Utworzyłem mały dokument Worda, który zapisałem jako HTML. Trzy panele na poniższym obrazku pokazują oryginalny dokument Word, dokument HTML wyświetlany w przeglądarce Microsoft Internet Explorer i dokument HTML wyświetlany w przeglądarce Google Chrome.

Oryginalne słowo, HTML wyświetlane przez IE i HTML wyświetlane przez Chrome

Jak wyjaśniono wcześniej, różnica między obrazami IE i Chrome wynika z dwukrotnego zapisania równań, raz w formacie WMZ i raz w formacie GIF. HTML jest za duży, aby go tu wyświetlić.

HTML utworzony przez makro to:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" 
                   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head><body>
<p>Some ordinary text.</p>
<p>H<sub>2</sub>SO<sub>4</sub>.</p>
<p>Abc &amp; def &gt; ghi &lt; jkl</p>
<p>x<sup>3</sup>+ x<sup>2</sup>+3x+4=0.</p><p></p>
<p><i>Equation</i>  </p>
<p>Mno</p>
<p><i>Equation</i></p>
</body></html>

Który wyświetla się jako:

HTML utworzony przez makro wyświetlane przez IE

Nie próbowałem konwertować równań, ponieważ bezpłatny zestaw MathType Software Development Kit najwyraźniej zawiera procedury konwertowane na LaTex

Kod jest dość prosty, więc niewiele komentarzy. Zapytaj, czy coś jest niejasne. Uwaga: jest to ulepszona wersja oryginalnego kodu.

Sub ConvertToHtml()

  Dim FileNum As Long
  Dim NumPendingCR As Long
  Dim objChr As Object
  Dim PathCrnt As String
  Dim rng As Word.Range
  Dim WithinPara As Boolean
  Dim WithinSuper As Boolean
  Dim WithinSub As Boolean

  FileNum = FreeFile
  PathCrnt = ActiveDocument.Path
  Open PathCrnt & "\TestWord.html" For Output Access Write Lock Write As #FileNum

  Print #FileNum, "<!DOCTYPE html PUBLIC ""-//W3C//DTD XHTML 1.0 Frameset//EN""" & _
                  " ""http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"">" & _
                  vbCr & vbLf & "<html xmlns=""http://www.w3.org/1999/xhtml"" " & _
                  "xml:lang=""en"" lang=""en"">" & vbCr & vbLf & _
                  "<head><meta http-equiv=""Content-Type"" content=""text/html; " _
                  & "charset=utf-8"" />" & vbCr & vbLf & "</head><body>"

  For Each rng In ActiveDocument.StoryRanges

    NumPendingCR = 0
    WithinPara = False
    WithinSub = False
    WithinSuper = False

    Do While Not (rng Is Nothing)
      For Each objChr In rng.Characters
        If objChr.Font.Superscript Then
          If Not WithinSuper Then
            ' Start of superscript
            Print #FileNum, "<sup>";
            WithinSuper = True
          End If
        ElseIf WithinSuper Then
          ' End of superscript
          Print #FileNum, "</sup>";
          WithinSuper = False
        End If
        If objChr.Font.Subscript Then
          If Not WithinSub Then
            ' Start of subscript
            Print #FileNum, "<sub>";
            WithinSub = True
          End If
        ElseIf WithinSub Then
          ' End of subscript
          Print #FileNum, "</sub>";
          WithinSub = False
          End If
          Select Case objChr
            Case vbCr
              NumPendingCR = NumPendingCR + 1
            Case "&"
              Print #FileNum, CheckPara(NumPendingCR, WithinPara) & "&amp;";
            Case "<"
              Print #FileNum, CheckPara(NumPendingCR, WithinPara) & "&lt;";
            Case ">"
              Print #FileNum, CheckPara(NumPendingCR, WithinPara) & "&gt;";
            Case Chr(1)
              Print #FileNum, CheckPara(NumPendingCR, WithinPara) & "<i>Equation</i>";
            Case Else
              Print #FileNum, CheckPara(NumPendingCR, WithinPara) & objChr;
          End Select
      Next
      Set rng = rng.NextStoryRange
    Loop
  Next

  If WithinPara Then
    Print #FileNum, "</p>";
    withpara = False
  End If

  Print #FileNum, vbCr & vbLf & "</body></html>"

  Close FileNum

End Sub
Function CheckPara(ByRef NumPendingCR As Long, _
                   ByRef WithinPara As Boolean) As String

  ' Have a character to output.  Check paragraph status, return
  ' necessary commands and adjust NumPendingCR and WithinPara.

  Dim RtnValue As String

  RtnValue = ""

  If NumPendingCR = 0 Then
    If Not WithinPara Then
      CheckPara = "<p>"
      WithinPara = True
    Else
      CheckPara = ""
    End If
    Exit Function
  End If

  If WithinPara And (NumPendingCR > 0) Then
    ' Terminate paragraph
    RtnValue = "</p>"
    NumPendingCR = NumPendingCR - 1
    WithinPara = False
  End If
  Do While NumPendingCR > 1
    ' Replace each pair of CRs with an empty paragraph
    RtnValue = RtnValue & "<p></p>"
    NumPendingCR = NumPendingCR - 2
  Loop
  RtnValue = RtnValue & vbCr & vbLf & "<p>"
  WithinPara = True
  NumPendingCR = 0

  CheckPara = RtnValue

End Function
Tony Dallimore
źródło
Świetna robota. Czy będzie działał dla wielu plików, czy musisz umieścić go w pliku, który chcesz przekonwertować?
mnmnc
@mnmnc. Dziękuję Ci. Myślę, że twoje rozwiązanie jest wrażeniem, chociaż prawdopodobnie jest jasne, że nie wierzę, że rozwiązanie, które zaczyna się od Microsoft HTML będzie działać. W wyniku pytania dotyczącego przepełnienia stosu pracuję nad konwersją Excela na HTML, ponieważ PublishObjects Microsoftu tworzy HTML nieakceptowalny dla większości (wszystkich?) Smartfonów. Mam niewielkie doświadczenie z programem Word VBA; Najlepiej radzę sobie z Excelem i Outlook VBA i kiedyś byłem dobry z Acess VBA. Wszystkie one pozwalają makrom w jednym pliku na dostęp do innych plików, więc jestem pewien, że to samo dotyczy programu Word.
Tony Dallimore,
0

Najprostszym sposobem na to jest po prostu następujące linie w VBA:

Sub testing()
With ActiveDocument.Content.Find
 .ClearFormatting
 .Format = True
 .Font.Superscript = True
 .Execute Forward:=True
End With

End Sub

Znajduje to cały tekst w indeksie górnym. Jeśli chcesz coś z tym zrobić, po prostu wstaw to do metody. Na przykład, aby znaleźć słowo „super” w indeksie górnym i przekształcić je w „super znaleziono”, użyj:

Sub testing()

With ActiveDocument.Content.Find
 .ClearFormatting
 .Format = True
 .Font.Superscript = True
 .Execute Forward:=True, Replace:=wdReplaceAll, _
 FindText:="super", ReplaceWith:="super found"
End With

End Sub
soandos
źródło