posts - 7 , comments - 64 , trackbacks - 0

Saturday, November 19, 2011

Converting .docx to pdf (or .doc to pdf, or .doc to odt, etc.) with libreoffice on a webserver on the fly using php

Ok, so I needed to convert .docx files to .pdf files on the fly, but none of the free php libraries that were available let me do it on my server (a webservice was not good enough).

Basically either I needed to pay for a library (and have it maybe suck) or just deal with the free ones that didn't convert the formatting well enough.

Not good enough!

I found that LibreOffice (OpenOffice's successor) allows command line conversion using the LibreOffice conversion engine (which DID preserve the formatting like I wanted and generally worked great).

I loaded the latest version of Ubuntu (http://www.ubuntu.com/download/ubuntu/download) onto my Virtual Box (https://www.virtualbox.org/wiki/Downloads) on my computer and found that I was able to easily convert files using the commandline like this:

libreoffice --headless -convert-to pdf fileToConvert.docx -outdir output/path/for/pdf

I thought: sweet...but I don't have admin rights on my host's web server. I tried to use a "portable" version of LibreOffice that I obtained from http://portablelinuxapps.org/ but I was unable to get it to work on my host's webserver, because my host's webserver didn't have all the dependencies (Dependency Hell! http://en.wikipedia.org/wiki/Dependency_hell)

I was at a loss of how to make it work, until I ran across a cool project made by a Ph.D. student (Philip J. Guo) at Stanford called CDE: http://www.pgbovine.net/cde.html

I will let you look at his explanations of how it works (I followed what he did here:

starting at about 32:00 as well as the directions on his site), but in short, it allows one to avoid dependency hell by copying all the files used when you run certain commands, recreating the linux environment where the command worked. I was able to use this to run LibreOffice without having to resort to someone's portable version of it, and it worked just like it did when I did it on Ubuntu with the command above, with a tweak: I needed to run the wrapper of LibreOffice the CDE generated.

So, below is my PHP code that calls it. In this code snippet, the filename to be copied is passed in as $_POST["filename"]. I copy the file to the same spot where I originally converted the file, convert it, copy it back and then delete all the files (so that it doesn't start growing exponentially).

I did it this way because I wasn't able to make it work otherwise on the webserver. If there is a linux + webserver ninja out there that can figure out how to make it work without doing this, I would be interested to know what you did. Please post a comment or something if you did that.

<?php
//first copy the file to the magic place where we can convert it to a pdf on the fly
copy($_POST["filename"], "../LibreOffice/cde-package/cde-root/home/robert/Desktop/".$_POST["filename"]);
//change to that directory
chdir('../LibreOffice/cde-package/cde-root/home/robert');
//the magic command that does the conversion
$myCommand = "./libreoffice.cde --headless -convert-to pdf Desktop/".$_POST["filename"]." -outdir Desktop/";
exec ($myCommand);
//copy the file back
copy("Desktop/".str_replace(".docx", ".pdf", $_POST["filename"]), "../../../../../documents/".str_replace(".docx", ".pdf", $_POST["filename"]));
//delete all the files out of the magic place where we can convert it to a pdf on the fly
$files1 = scandir('Desktop');
//my files that I generated all happened to start with a number.
$pattern = '/^[0-9]/';
foreach ($files1 as $value)
{
preg_match($pattern, $value, $matches);
if(count($matches) > 0)
{
unlink("Desktop/".$value);
}
}
//changing the header to the location of the file makes it work well on androids
header( 'Location: '.str_replace(".docx", ".pdf", $_POST["filename"]) );
?>

And here is the tar.gz file I generated I generated with CDE. See below for a working example and complete, documented code.

Success! I made a truly portable version of LibreOffice that can convert files on the fly on a webserver using 100% free, open source software!

Note: since when I used CDE I only converted a .docx to a .pdf, my tar.gz file above will probably only work to do that. To get it to do other things, you will have to do them with CDE first.

*****************************************************************************

UPDATE: since several people have had questions on how to get it working or had issues making it work, I am putting a complete working example out there for you to play with and modify.

Click here for working example.

And here is the tar.gz of the working example, tied up in a nice bow for you. To make sure the permissions don't get screwed up, I recommend uploading the tar.gz file to your server and then unpacking it there.

This is my way of giving back to all the great people out there that have helped me out by doing these kinds of things for me. Pay it forward, guys! [licensed under the MIT license.]

Posted On Saturday, November 19, 2011 6:07 PM | Comments (54) |

Tuesday, April 5, 2011

PL/SQL: get the id if a value exists in a look up table, insert and get the id if it doesn't

I ran into this one because I was working with some tables in a normalized database and I needed to quickly check to see if an id existed, and if it didn't, I needed to insert and then get the id.

This is what I came up with in the end: inside your PL/SQL block, you can have any number of sub blocks, like this (here I am using 2 PL/SQL variables: thisVehicleFuelTypeId and thisVehicleFuelType):

  BEGIN  ---------- sub-block begins
   SELECT FUEL_TYPEID INTO thisVehicleFuelTypeId FROM TBL_FUEL_TYPE WHERE DESCRIPTION = thisVehicleFuelType;
   EXCEPTION
      WHEN NO_DATA_FOUND THEN
      INSERT INTO TBL_FUEL_TYPE (DESCRIPTION)
         VALUES (thisVehicleFuelType) RETURNING FUEL_TYPEID INTO thisVehicleFuelTYpeId;
   END;  ---------- sub-block ends

First, I am trying to SELECT INTO the ID I need into a PL/SQL variable. If that works, then I win and I move on.

If that value does not exist, then Oracle will throw a NO_DATA_FOUND exception which you catch and then do an INSERT INTO with a RETURNING, so that you get the id that you wanted, maintaining a nicely normalized look up table!

Posted On Tuesday, April 5, 2011 10:12 AM | Comments (2) |

Wednesday, March 30, 2011

Allowing or blocking ip addresses with PHP and mySQL based off of what country they are from

Ok here is a cool trick I figured out that I hope will be helpful to someone:

What if you wanted to block all the incoming ip addresses from a given country to a given site using only PHP and mySQL (without using .htaccess, etc.) and be able to log who you blocked into your mySQL database?

First, you need to have a white list or a black list. I got mine from http://software77.net/geo-ip/ because they were free, based off of countries and worked pretty darn well.

For myself, I used the x.x.x.x-y.y.y.y option, cut out the headers and dropped the text file onto my server, then I made a little script to load them into a table in mydatabase:

<?php
include "myconfig.php"; //this contains my db connection info
--use your own here
include "openmyDB.php"; //this opens up my db connection --use your own here

//clear out the table first
mysql_query("DELETE FROM myallowedIp")or die(mysql_error()); 

//get all the ip ranges from the text file I put on the server
$validAddressList = file_get_contents('validipAddresses.txt');

//split them up into an array
$validAddresses = explode("\n", $validAddressList);

//put each element of the array into the db
foreach ($validAddresses as $validAddressRange)
{

    //trim it
    $validAddressRange = trim($validAddressRange);

    //get the two halves of the range
    $fromAndTo = explode("-", $validAddressRange);

    //put them into the db. NOTE: I am converting them into longs so thatI can quickly compare them later
    $myQuery = "INSERT INTO myallowedIp (ipaddressFrom, ipaddressTo)
        VALUES ('".ip2long($fromAndTo[0])."', '".ip2long($fromAndTo[1])."')";

    //echo $myQuery."<br>";
    mysql_query($myQuery)or die(mysql_error());
}

include "closemyDB.php"; //this closes my db connection
--use your own here

?>

I recommend taking that script off your server once you are done loading it up for security purposes.

Now that I have it all loaded into my db, I can now query that table every time a user shows up at my site and turn them away or allow them in (depending on if I want to treat my address ranges like a whitelist or a blacklist)!

To do this, first you get the ip address of the user (here I keep it around as a session variable, but do whatever you want):

<?php
session_start();

if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))   //to check ip is pass from proxy
{
    $ip=$_SERVER['HTTP_X_FORWARDED_FOR'];
}
else
{
    $ip=$_SERVER['REMOTE_ADDR'];
}
   
$_SESSION["REALIPADDRESS"] = $ip;

?>

Then, you compare it against your table (assuming you have already opened up your db connections):

    $allowResult = mysql_query("SELECT * FROM myallowedIp WHERE
        ipaddressFrom <= '".ip2long($_SESSION["REALIPADDRESS"])."' AND
        ipaddressTo >= '".ip2long($_SESSION["REALIPADDRESS"])."'")or die(mysql_error()); 
    if($allowRow = mysql_fetch_array( $allowResult ))
    {
        //if this is a whitelist, you can come in...(put other code here as you want)
    }
    else
    {
        //not on whitelist...

      //send them somewhere else or whatever you want. Note: only use the header() command if you have not sent ANYTHING to the user yet. If it is not working, check that first.
      header ('HTTP/1.1 301 Moved Permanently');
      header ('Location: http://www.google.com/');
      exit();
    }

I hope that helps someone! Take care and have fun developing!


EDIT: I had to move the site around where I use this, and I decided to change how I did it to make it more intuitive for me to test. Instead of storing the allowable IP addresses as LONGs, I stored them as VARCHAR with a max size of 15, so that my code snippet above would look like this instead:

    $myQuery = "INSERT INTO myallowedIp (ipaddressFrom, ipaddressTo) 
        VALUES ('".$fromAndTo[0]."', '".$fromAndTo[1]."')";

Then, when it comes time to check if something matches (or doesn't match), I do this instead, using mySQL's INET_ATON to compare them instead of PHP's ip2long: 

$allowResult = mysql_query("SELECT * FROM myallowedIp WHERE 

INET_ATON(ipaddressFrom) <= INET_ATON('".$_SESSION["REALIPADDRESS"]."') AND 

INET_ATON(ipaddressTo) >= INET_ATON('".$_SESSION["REALIPADDRESS"]."')")or die(mysql_error()); 


Posted On Wednesday, March 30, 2011 3:12 PM | Comments (0) |

Thursday, January 20, 2011

Getting Silverlight-enabled WCF Service to work with IIS 7 and windows server 2008 with https and windows authentication

Getting this silly thing to work turned out to be a painful experience, so I am recording my lessons learned so that if some other downtrodden programmer wants to see what I did, they will be able to figure it out faster (hopefully):

Ok, so say you have a silverlight application and you want to talk to a database. Silverlight is Client-based, and Databases are Server based. This is a problem. You can either pass the values in initially (not very good for interactive stuff) or you can make a WCF service.

I needed interactivity, so the best way to make it work is with a WCF service. There are many ways to implement one of these, but the way I picked and got working was having the service be part of my .Web project. (I thought it was the easiest looking one)

First, I went to my .Web project in my solution and told it I wanted to add a new item, and picked the "Silverlight-enabled WCF Service" from the "Silverlight" category of the installed templates. Doing so adds a .svc file and an associated .cs file to your project and has an example method "DoWork()" that you can use to create other methods that can be called from silverlight.

Basically you can do whatever you would normally do from a .cs file, as long as you have appropriate references and assemblies and put "[OperationContract]" above it, like the example method that it comes with.  Go ahead and do your thing like you would normally do.

Then, you need to go over to your service references in your silverlight project and tell it you want to add a service reference. Since you are still in developing mode at this point, just click on "Discover" and pick your service, and name it whatever you want, like "myWCFService" or something. That is what I used, so my example code will have it named as such.

Now, in your silverlight project, you will be able to create a myWCFService client and call asynchronous methods on it like so in your .cs files:

myWCFService.Service1Client client = new myWCFService.Service1Client();

client.getMapPointsCompleted +=new EventHandler<myWCFService.getMapPointsCompletedEventArgs>(client_getMapPointsCompleted);
client.getMapPointsAsync();

(and of course define the async callback method--in this case "client_getMapPointsCompleted"-- in your code)

Now, you will be able to develop it and make it work on your localhost without any issue. The problem (and the part that annoyed me for several hours is trying to get the silly thing to work on a production server):

For me, I was making my project for an intranet, and the sysadmins wanted to have it be https, with windows authentication. When you make the service like I described above, it defaults to http with no security whatsoever. Also, the service will point to localhost, which is fine and good for developing, but will need to be changed when we move it to production.

I spent a lot of time looking at different solutions for the problem. The one I came up with was to change my system.serviceModel in web.config to the following:

  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpEndpointBinding">
          <security mode="Transport">
            <transport clientCredentialType="Windows" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
        multipleSiteBindingsEnabled="true" />
    <services>
      <service name="[**put the name of your .web project here**].Service1">
        <endpoint address="" binding="basicHttpBinding" bindingConfiguration="BasicHttpEndpointBinding"
            name="BasicHttpEndpoint" contract="[**put the name of your .web project here**].Service1" />
      </service>
    </services>
  </system.serviceModel>

Most of that should be generated for you. The main change for me was changing it from "customBinding" to "basicHttpBinding," setting the security mode to "transport," the credential type to "windows" and deleting the mex binding (not sure what it was supposed to do, but it was making it so that I couldn't change the credential type from anonymous, and it works fine without the mex binding).

After I did this, I published my .Web project to the server, making sure that IIS's settings matched up with it being https with windows authentication, etc. I then went to

https://[addressOfMysilverlightapplication]/Service1.svc

to make sure that my service worked. If it doesn't show you an error when you did that, you got the service working. Good job. You are most of the way there. If it gives you an error, you obviously did something wrong with your web.config or your IIS 7 settings or you typed in the url wrong. Fix the url or Google the error and fix.

Now to make it so that your production silverlight application will talk to this service. Remember the service reference we made earlier? Delete it. Make a new service reference, but this time, instead of clicking on "discover," enter the https address you just used to verify your service worked and name it the same as the one you just deleted.

Then, go to your ServiceReferences.ClientConfig and change it so that it is a relative address (just in case you move it to a different location on the server or a different server later, it won't break). Mine looked like this:

<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpEndpoint" maxBufferSize="2147483647"
                    maxReceivedMessageSize="2147483647">
                    <security mode="Transport" />
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="../Service1.svc"
                binding="basicHttpBinding" bindingConfiguration="BasicHttpEndpoint"
                contract="myWCFService.Service1" name="BasicHttpEndpoint" />
        </client>
    </system.serviceModel>
</configuration>

If you don't have your service on the root of your project, you will have to type in something different for your address.

Rebuild and publish it again, and then take a look at your silverlight application on the server. It should work perfectly now, using https and windows authentication while being able to use a WCF service.

I hope this helped someone. Please feel free to leave a comment.

Posted On Thursday, January 20, 2011 9:39 AM | Comments (4) |

Wednesday, March 24, 2010

Write, Read and Update Oracle CLOBs with PL/SQL

Fun with CLOBS! If you are using Oracle, if you have to deal with text that is over 4000 bytes, you will probably find yourself dealing with CLOBs, which can go up to 4GB. They are pretty tricky, and it took me a long time to figure out these lessons learned. I hope they will help some down-trodden developer out there somehow.

Here is my original code, which worked great on my Oracle Express Edition: (for all examples, the first one writes a new CLOB, the next one Updates an existing CLOB and the final one reads a CLOB back)

CREATE OR REPLACE PROCEDURE PRC_WR_CLOB (
       p_document      IN VARCHAR2,
       p_id            OUT NUMBER)

IS
     lob_loc CLOB;

BEGIN

   INSERT INTO TBL_CLOBHOLDERDDOC (CLOBHOLDERDDOC)
       VALUES (empty_CLOB())
       RETURNING CLOBHOLDERDDOC, CLOBHOLDERDDOCID INTO lob_loc, p_id;

   DBMS_LOB.WRITE(lob_loc, LENGTH(UTL_RAW.CAST_TO_RAW(p_document)),
1, UTL_RAW.CAST_TO_RAW(p_document));


END;
/


CREATE OR REPLACE PROCEDURE PRC_UD_CLOB (
       p_document      IN VARCHAR2,
       p_id            IN NUMBER)

IS
     lob_loc CLOB;

BEGIN

       SELECT CLOBHOLDERDDOC INTO lob_loc FROM TBL_CLOBHOLDERDDOC
       WHERE CLOBHOLDERDDOCID = p_id FOR UPDATE;

   DBMS_LOB.WRITE(lob_loc, LENGTH(UTL_RAW.CAST_TO_RAW(p_document)),
1, UTL_RAW.CAST_TO_RAW(p_document));

END;
/



CREATE OR REPLACE PROCEDURE PRC_RD_CLOB (
   p_id IN NUMBER,
   p_clob OUT VARCHAR2)
IS

   lob_loc  CLOB;

BEGIN

   SELECT CLOBHOLDERDDOC INTO lob_loc
   FROM   TBL_CLOBHOLDERDDOC
   WHERE  CLOBHOLDERDDOCID = p_id;

   p_clob := UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(lob_loc,
DBMS_LOB.GETLENGTH(lob_loc), 1));

END;
/

As you can see, I had originally been casting everything back and forth between RAW formats using the UTL_RAW.CAST_TO_VARCHAR2() and UTL_RAW.CAST_TO_RAW() functions all over the place, but it had the nasty side effect of working great on my Oracle express edition on my developer box, but having all the CLOBs above a certain size display garbage when read back on the Oracle test database server .

So...I kept working at it and came up with the following, which ALSO worked on my Oracle Express Edition on my developer box:

 

CREATE OR REPLACE PROCEDURE PRC_WR_CLOB (
    p_document      IN VARCHAR2,
    p_id        OUT NUMBER)

IS
      lob_loc CLOB;

BEGIN

    INSERT INTO TBL_CLOBHOLDERDOC (CLOBHOLDERDOC)
        VALUES (empty_CLOB())
        RETURNING CLOBHOLDERDOC, CLOBHOLDERDOCID INTO lob_loc, p_id;

    DBMS_LOB.WRITE(lob_loc, LENGTH(p_document), 1, p_document);

 
END;
/

CREATE OR REPLACE PROCEDURE PRC_UD_CLOB (
    p_document      IN VARCHAR2,
    p_id        IN NUMBER)

IS
      lob_loc CLOB;

BEGIN

    SELECT CLOBHOLDERDOC INTO lob_loc FROM TBL_CLOBHOLDERDOC
    WHERE CLOBHOLDERDOCID = p_id FOR UPDATE;

    DBMS_LOB.WRITE(lob_loc, LENGTH(p_document), 1, p_document);

END;
/


CREATE OR REPLACE PROCEDURE PRC_RD_CLOB (
    p_id IN NUMBER,
    p_clob OUT VARCHAR2)
IS

    lob_loc  CLOB;

BEGIN

    SELECT CLOBHOLDERDOC INTO lob_loc
    FROM   TBL_CLOBHOLDERDOC
    WHERE  CLOBHOLDERDOCID = p_id;

    p_clob := DBMS_LOB.SUBSTR(lob_loc, DBMS_LOB.GETLENGTH(lob_loc), 1);

END;
/

Unfortunately, by changing my code to what you see above, even though it kept working on my Oracle express edition, everything over a certain size just started truncating after about 7950 characters on the test server!

Here is what I came up with in the end, which is actually the simplest solution and this time worked on both my express edition and on the database server (note that only the read function was changed to fix the truncation issue, and that I had Oracle worry about converting the CLOB into a VARCHAR2 internally):

CREATE OR REPLACE PROCEDURE PRC_WR_CLOB (
       p_document      IN VARCHAR2,
       p_id            OUT NUMBER)

IS
     lob_loc CLOB;

BEGIN

   INSERT INTO TBL_CLOBHOLDERDDOC (CLOBHOLDERDDOC)
       VALUES (empty_CLOB())
       RETURNING CLOBHOLDERDDOC, CLOBHOLDERDDOCID INTO lob_loc, p_id;

   DBMS_LOB.WRITE(lob_loc, LENGTH(p_document), 1, p_document);


END;
/


CREATE OR REPLACE PROCEDURE PRC_UD_CLOB (
       p_document      IN VARCHAR2,
       p_id            IN NUMBER)

IS
     lob_loc CLOB;

BEGIN

       SELECT CLOBHOLDERDDOC INTO lob_loc FROM TBL_CLOBHOLDERDDOC
       WHERE CLOBHOLDERDDOCID = p_id FOR UPDATE;

   DBMS_LOB.WRITE(lob_loc, LENGTH(p_document), 1, p_document);

END;
/



CREATE OR REPLACE PROCEDURE PRC_RD_CLOB (
   p_id IN NUMBER,
   p_clob OUT VARCHAR2)
IS

BEGIN

   SELECT CLOBHOLDERDDOC INTO p_clob
   FROM   TBL_CLOBHOLDERDDOC
   WHERE  CLOBHOLDERDDOCID = p_id;

END;
/

 

I hope that is useful to someone!

Posted On Wednesday, March 24, 2010 2:41 PM | Comments (4) |

Thursday, March 18, 2010

Lessons learned from Word 2007 automation with c# 2008

My organization has an ongoing project to take documents produced for internal regulations and such, change some of the formatting and then export it as PDF.

Our requirements were that only one person would be doing this, but it has been painfully tedious and sometimes error-prone to do by hand. Enter the fearless developer to automate the situation!

Since I am one of those guys that just plain does not like VB, I wanted to do the automation in the ever-so-much-more-familiar C#. While Microsoft had made a dll that makes such a task easier, documentation on MSDN is pretty lame and most of the forums and posts on the internet had little to do with my task.

So, I feel like I can give back to the community and make a post here of the things I have learned so far. I hope this is helpful to whoever stumbles upon it.

Steps to do this:

1) First of all, make some sort of a project and use some sort of a means to get the filename of the word document you are trying to open. I got the filename the user wanted with an openFileDialog tied to a button that I labeled 'Browse':

       private void btnBrowse_Click(object sender, EventArgs e)
       {
           try
           {
               DialogResult myResult = openFileDialog1.ShowDialog();
               if (myResult.Equals(DialogResult.
OK))
               {
                   if (openFileDialog1.SafeFileName.
EndsWith(".doc"))
                   {
                       txtFileName.Text = openFileDialog1.SafeFileName;
                       paramSourceDocPath = openFileDialog1.FileName;
                       paramExportFilePath = openFileDialog1.FileName.
Replace(".doc", ".pdf");
                   }
                   else
                   {
                       txtFileName.Text = "only something that end with .doc, please";
                   }
               }
           }
           catch (Exception err)
           {
               lblError.Text = err.Message;
           }
       }

 

2) Add in "using Microsoft.Office.Interop.Word;" after setting your project to reference Microsoft.Office.Core and Microsoft.Office.Interop.Word so that you don't have to add "Microsoft.Office.Interop.Word" to the front of everything.

3) Now you are ready to play. You will need to have a copy of word open and a copy of your word document that you want to modify open to be able to make the changes that are needed.

The word interop dll likes using ref on all the parameters passed in, and likes to have them as objects. If you don't want to specify the parameter, you have to give it a "Type.Missing". I suggest creating some objects that you reuse all over the place to maintain sanity.

object paramMissing = Type.Missing;

ApplicationClass wordApplication = new ApplicationClass();

Document wordDocument = wordApplication.Documents.Open(
               ref paramSourceDocPath, ref paramMissing, ref paramMissing,
               ref paramMissing, ref paramMissing, ref paramMissing,
               ref paramMissing, ref paramMissing, ref paramMissing,
               ref paramMissing, ref paramMissing, ref paramMissing,
               ref paramMissing, ref paramMissing, ref paramMissing,
               ref paramMissing);

4) There are many ways to modify the text of the inside of the word document. One of the ways that was most effective for me was to break it down by paragraph and then do things on each paragraph by what style the particular paragraph had.

           foreach (Paragraph thisParagraph in wordDocument.Content.Paragraphs)

           {
               string strStyleName = ((Style)thisParagraph.get_
Style()).NameLocal;
               string strText = thisParagraph.Range.Text;

               //Do whatever you need to do
           }

5) Sometimes you want to insert a new line character somewhere in the text or insert text into the document, etc.  There are a few ways you can do this: you can either modify the text of a paragraph by doing something like this ('\r' makes a new paragraph, '\v' will make a newline without making a new paragraph. If you remove a '\r' from the text, it will eliminate the paragraph you removed it from):

thisParagraph.Range.Text = "A\vNew Paragraph!\r" + thisParagraph.Range.Text;

OR

you could select where you want to insert it and have it act like you were typing in Word like any normal user (note: if you do not collapse the range first, you will overwrite the thing you got the range from)

object oCollapseDirectionEnd = WdCollapseDirection.wdCollapseEnd;
object oCollapseDirectionStart = WdCollapseDirection.
wdCollapseStart;

Range rangeInsertAtBeginning = thisParagraph.Range;

Range rangeInsertAtEnd = thisParagraph.Range;

rangeInsertAtBeginning.Collapse(ref oCollapseDirectionStart);

rangeInsertAtEnd.Collapse(ref oCollapseDirectionEnd);

rangeInsertAtBeginning.Select();

wordApplication.Selection.TypeText("Blah Blah Blah");

rangeInsertAtEnd.Select();

wordApplication.Selection.TypeParagraph();

6) If you want to make text columns, like a newspaper or newsletter, you have to modify the page layout of the document or a section of the document to make it happen. In my case, I only wanted a particular section to have that, and I wanted to have a black line before and after the newspaper-like text columns. First you need to do a section break on either side of what you wanted, then you take the section and modify the page layout. Then you can modify the borders of the section (or another object in the word document). I also show here how to modify the alignment of a paragraph.

           object oSectionBreak = WdBreakType.wdSectionBreakContinuous;

           //These ranges were set while I was going through the paragraphs of my document, like I was showing earlier

           rangeHeaderStart.InsertBreak(ref oSectionBreak);
           rangeHeaderEnd.InsertBreak(ref oSectionBreak);

           //change the alignment to justify
           object oRangeHeaderStart = rangeStartJustifiedAlignment.
Start;
           object oRangeHeaderEnd = rangeHeaderEnd.End;
           Range rangeHeader = wordDocument.Range(ref oRangeHeaderStart, ref oRangeHeaderEnd);
           rangeHeader.Paragraphs.
Alignment = WdParagraphAlignment.wdAlignParagraphJustify;

           //find the section break and make it into triple text columns
           foreach (Section mySection in wordDocument.Sections)
           {
               if (mySection.Range.Start == rangeHeaderStart.Start)
               {
                   mySection.PageSetup.
TextColumns.Add(ref paramMissing, ref paramMissing, ref paramMissing);
                   mySection.PageSetup.
TextColumns.Add(ref paramMissing, ref paramMissing, ref paramMissing);

                   //I didn't like the default spacing and column widths. This is how I adjusted them.
                   foreach (TextColumn txtc in mySection.PageSetup.
TextColumns)
                   {
                       try
                       {
                           txtc.SpaceAfter = 151.6f;
                           txtc.Width = 7;
                       }
                       catch (Exception)
                       {
                           txtc.Width =
151.6f;
                       }
                   }
               }
           }

That is all  I have time for today! I hope this was helpful to someone!


 

Posted On Thursday, March 18, 2010 2:20 PM | Comments (0) |

Wednesday, March 17, 2010

A new blog is born

Hello! I have decided to start a blog of my adventures learning how to solve problems coding things with the intent that what I put out there might be of some use to some down-trodden developer out there that is trying to solve a problem that I have already figured out.

Posted On Wednesday, March 17, 2010 11:08 AM | Comments (0) |

Powered by: