Como iterar programaticamente através de subscritos, sobrescritos e equações encontradas em um documento do Word

12

Eu tenho alguns documentos do Word, cada um contendo algumas centenas de páginas de dados científicos, que incluem:

  • Fórmulas químicas (H2SO4 com todos os subscritos e sobrescritos adequados)
  • Números científicos (expoentes formatados usando sobrescritos)
  • Muitas equações matemáticas. Escrito usando o editor de equações matemáticas no Word.

O problema é que armazenar esses dados no Word não é eficiente para nós. Então, queremos armazenar todas essas informações em um banco de dados (MySQL). Queremos converter a formatação para LaTex.

Existe alguma maneira de percorrer todos os sub-scripts, sobrescritos e equações em um documento do Word usando o VBA?

garras
fonte
Você já pensou em extrair os dados xml de dentro do documento? Todos os Microsoft Documents 2007+ (.docx) são basicamente arquivos xml compactados. Você pode recuperar aqueles usando um analisador xml.
James Mertz
era muito longo para postar como comentário, então adicionei como resposta.
James Mertz

Respostas:

12

Sim existe. Eu sugeriria usar o Powershell, pois ele lida com arquivos do Word muito bem. Eu acho que vou ser a maneira mais fácil.

Mais sobre automação do Powershell vs Word aqui: http://www.simple-talk.com/dotnet/.net-tools/com-automation-of-office-applications-via-powershell/

Eu cavei um pouco mais fundo e encontrei este script do 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();

Salve-o como .ps1 e inicie-o com:

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

Ele salvará todo o arquivo .doc do diretório especificado, como os arquivos html. Então, eu tenho um arquivo doc no qual eu tenho o seu H2SO4 com subscritos e após a conversão do PowerShell, a saída é a seguinte:

<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>

Como você pode ver, os subscritos têm suas próprias tags em HTML; portanto, o que resta é analisar o arquivo em bash ou c ++ para cortar de corpo para / corpo, altere para LATEX e remova o restante das tags HTML posteriormente.

Código de http://blogs.technet.com/b/bshukla/archive/2011/09/27/3347395.aspx


Então, eu desenvolvi um analisador em C ++ para procurar por subscrito HTML e substituí-lo por subscrito LATEX.

O código:

#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;
}

Para o arquivo 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>

A saída é:

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

Não é ideal, é claro, mas tratar é como prova de conceito.

mnmnc
fonte
3

Você pode extrair o xml diretamente de qualquer documento do Office com mais de 2007. Isso é feito da seguinte maneira:

  1. renomeie o arquivo de .docx para .zip
  2. extrair o arquivo usando 7zip (ou algum outro programa de extração)
  3. Para o conteúdo real do documento, procure na pasta extraída sob a wordsubpasta e o document.xmlarquivo. Isso deve conter todo o conteúdo do documento.

insira a descrição da imagem aqui

Eu criei um documento de amostra e, nas tags do corpo, encontrei isso (note que eu o juntei rapidamente, então a formatação pode ficar um pouco fora):

<?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>

Parece que a <w:t>tag é para texto, <w:rPr>é a definição da fonte e do<w:p> é um novo parágrafo.

A palavra equivalente é assim:

insira a descrição da imagem aqui

James Mertz
fonte
2

Eu tenho olhado para uma abordagem diferente daquela adotada pelo mnmnc.

Minhas tentativas de salvar um documento do Word de teste como HTML não foram bem-sucedidas. Descobri no passado que o HTML gerado pelo Office é tão cheio de palha que escolher os bits que você deseja é quase impossível. Eu descobri que esse é o caso aqui. Eu também tive um problema com equações. O Word salva equações como imagens. Para cada equação, haverá duas imagens, uma com uma extensão de WMZ e outra com uma extensão de GIF. Se você exibir o arquivo html no Google Chrome, as equações parecerão boas, mas não maravilhosas; a aparência corresponde ao arquivo GIF quando exibida com uma ferramenta de exibição / edição de imagens que pode lidar com imagens transparentes. Se você exibir o arquivo HTML no Internet Explorer, as equações parecerão perfeitas.

Informação adicional

Eu deveria ter incluído esta informação na resposta original.

Criei um pequeno documento do Word que salvei como HTML. Os três painéis da imagem abaixo mostram o documento original do Word, o documento HTML, conforme exibido pelo Microsoft Internet Explorer, e o documento HTML, como exibido pelo Google Chrome.

Palavra original, HTML exibida pelo IE e HTML exibida pelo Chrome

Como explicado anteriormente, a diferença entre as imagens do IE e do Chrome é o resultado das equações serem salvas duas vezes, uma vez no formato WMZ e outra no formato GIF. O HTML é muito grande para ser mostrado aqui.

O HTML criado pela macro é:

<!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>

Que é exibido como:

HTML criado pela macro, conforme exibido pelo IE

Não tentei converter as equações desde o Kit de desenvolvimento de software gratuito MathType aparentemente inclui rotinas que se convertem em LaTex

O código é bastante básico, portanto não há muitos comentários. Pergunte se algo não está claro. Nota: esta é uma versão aprimorada do código original.

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
fonte
Ótimo trabalho. Funcionará em vários arquivos ou você deve colocá-lo no arquivo que deseja converter?
mnmnc 31/07/12
@mnmnc. Obrigado. Acho que sua solução é impressa, embora provavelmente esteja claro que não acredito que uma solução que comece com o Microsoft Html funcione. Como resultado de uma pergunta de estouro de pilha, estou trabalhando na conversão do Excel para HTML porque o PublishObjects da Microsoft cria o HTML inaceitável para a maioria dos smartphones (todos?). Tenho pouca experiência com o Word VBA; Sou o melhor com Excel e Outlook VBA e costumava ser bom com o Access VBA. Todos eles permitem que uma macro em um arquivo acesse outros arquivos, por isso tenho certeza que o mesmo se aplica ao Word.
Tony Dallimore
0

A maneira mais simples de fazer isso é apenas as seguintes linhas no VBA:

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

End Sub

Isso encontrará todo o texto sobrescrito. Se você quiser fazer algo com ele, basta inseri-lo no método Por exemplo, para encontrar a palavra "super" em um sobrescrito e transformá-la em "super encontrado", use:

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
fonte