SiteExperts.com Logo Home | Community | Developer's Paradise | Jobs
User Groups | Site Tools | Site Information | Search

Inside Technique : Building a Content Server with XML and ASP : Saving Files

Saving an article takes a few steps. The first step is to verify that the article is valid XML. After validating the article, depending on if the user creates a new article or updated an existing one, slightly different actions occur. For new articles we:

  1. get the next available article ID,
  2. insert the new article into the index,
  3. write the new index to disk,
  4. write the article to disk,
  5. and finally clear the index from the disk cache.

For existing articles, we

  1. find the existing article in the index,
  2. update the title and author,
  3. write the index to disk,
  4. overwrite the existing article on disk,
  5. and finally clear the index and article from the disk cache

The first step is to validate the XML. The script that validates the XML merely ensures the XML is valid. In a production environment, you are also going to want to validate the articles against a DTD to ensure that they are really articles. This should be done so you can ensure reasonable results when the article is applied to the XSL sheet. We test the article by simply trying to parse it with the XML parser:

  ' Get the submitted story
  sStory = request.form("story")

  ' Instantiate the parser
  set sourceFile = Server.CreateObject("Microsoft.XMLDOM")
  sourceFile.async = false
  ' Try loading the story
  if not sourceFile.loadXML(sStory) then
    ' Returns false if fails
    response.write(reportParseError(sourceFile.parseError))
  else
    ' parsed successfully
    ' do save here...
  end if

Once successfully parsed, we need to save the document. Depending on if the edited article is new or an existing one we have slightly different processing. For existing articles, we locate the article node in the index, update as appropriate, and then save the new article to disk. The process for updating the article is the reverse of how we retrieved existing articles.

  ' parsed successfully
  if (storyid<>"") then
    set nodeStory = getNode(indexFile,storyID)
    if not nodeStory is nothing then
      ' Story found
      ' Update title and author by finding
      ' title and author text nodes
      nodeStory.selectsinglenode("title[.]").text=sTitle
      nodeStory.selectsinglenode("author[.]").text = sAuthor
      ' Write the existing story to disk
      sourceFile.save(server.mappath("src/" & (storyid) & ".xml"))
    end if
  else
    ' New Article
  end if

Adding articles is a bit more involved. We chose to manually construct the sub-tree representing the article. This involves creating the individual article, id, title, and author nodes and then inserting them into the appropriate part of the document.

  ' Get the next article id
  Set nodeStory = indexFile.selectSingleNode("storyindex")
  if nodeStory.hasChildNodes then
    Set nodeArticle = nodeStory.lastChild
    idArticle = nodeArticle.selectSingleNode("id").text
  else
    idArticle = 0
  end if
  Set nodeNew = indexFile.createNode(1,"article","")
  Set nodeID = indexFile.createNode(1,"id","")
  Set nodeText = indexFile.createTextNode(idArticle+1)
  nodeID.appendChild(nodeText)
  Set nodeTitle = indexFile.createNode(1,"title","")
  Set nodeTitleText = indexFile.createTextNode(sTitle)
  nodeTitle.appendChild(nodeTitleText)
  Set nodeAuthor = indexFile.createNode(1,"author","")
  Set nodeAuthorText = indexFile.createTextNode(sAuthor)
  nodeAuthor.appendChild(nodeAuthorText)
  nodeNew.appendChild(nodeID)
  nodeNew.appendChild(nodeTitle)
  nodeNew.appendChild(nodeAuthor)
  nodeStory.appendChild(nodeNew)
  storyID = idArticle + 1
  sourceFile.save(server.mappath("src/" & storyID & ".xml"))

We create nodes using the createNode method. Text is inserted into the node using the createTextNode method. Finally, the nodes can be combined using the appendChild method. Walking through the above script step-by-step to add an article, we build up the following tree (for a new article with the ID of 3):

Step 1
Set nodeNew = indexFile.createNode(1,"article","")
Set nodeID = indexFile.createNode(1,"id","")
Set nodeText = indexFile.createTextNode(idArticle+1)
nodeID.appendChild(nodeText)

Generates...

article     id
             |
            "3"

Step 2
Set nodeTitle = indexFile.createNode(1,"title","")
Set nodeTitleText = indexFile.createTextNode(sTitle)
nodeTitle.appendChild(nodeTitleText)

Generates...

article     id           title
             |             |
            "3"     "Cool New Article"

Step 3
Set nodeAuthor = indexFile.createNode(1,"author","")
Set nodeAuthorText = indexFile.createTextNode(sAuthor)
nodeAuthor.appendChild(nodeAuthorText)

Generates...

article     id           title           author
             |             |                |
            "3"     "Cool New Article"   "Scott"


Step 4
nodeNew.appendChild(nodeID)

Generates...

article        title            author
   |             |                 |
  id     "Cool New Article"     "Scott"
   |
  "3"

Step 5
nodeNew.appendChild(nodeTitle)

Generates...

       article             author
          |                   |
    +-----+-----+             |
   id         title        "Scott"
    |           |
   "3"  "Cool New Article" 

Step 6
nodeNew.appendChild(nodeAuthor)

Generates...

              article
                 |             
    +------------+------------+
   id          title        Author
    |            |            |
   "3"   "Cool New Article" "Scott"


This entire sub-tree is then inserted back into the index.xml file. If you are familiar to innerHTML and outerHTML methods in DHTML, this approach may appear more tedious. However, this approach provides greater control (especially when editing existing elements) and is much less error-prone as you are not manipulating a string representation of the elements.

The last remaining action in the editor is to delete an article. Deleting the article is extremely simple. We just need to remove the article node from the index:

if request.form("Delete")<>"" and storyid<>"" then
  ' Delete the Story
  Set indexFile = Server.CreateObject("Microsoft.XMLDOM")
  indexFile.async = false
  indexFile.load server.mappath("src/index.xml")
  if (indexFile.parseError.errorCode <> 0) then
    response.write(reportParseError(indexFile.parseError))
  else
    ' Find story
    set nodeStory = indexFile.selectSingleNode("storyindex/article[id[.=" _
      & chr(34) & storyid & chr(34) & "]]")
    if not nodeStory is nothing then
      ' Remove node
      nodeStory.parentNode.removeChild(nodeStory)
      ' Save new index
      indexFile.save(server.mapPath("src/index.xml"))
      response.write("<B>Deleted</B><P>")
      ' Remove cached files
      delFile(server.mappath("cache/index.inc"))
      delFile(server.mappath("cache/" & storyID & ".inc"))
      ' We purposely leave the raw XML file on disk 
      ' to enable last resort recovery 
      storyID=""
    end if
  end if
end if

When reviewing our implementation, you should understand a design decision that simplifies how our content server updates the index file. Our system has no intelligent locking mechanism or transaction processing. To an end-user this means our system should not be used in a high-volume collaborative environment. If two users were to save an article at the same time, there is the risk one of the articles will overwrite the other since we do not ensure that the next index is valid. The same holds true if an existing article is saved at the same time a new article is added. One of the operations may not be properly saved. In an environment with few or very infrequent edits, our decision may never expose any problems. In either case, you need to be aware of this.

This concludes the discussion of the Content Server implementation. Next we explain how to download and install the sample files. Since we presented the ASP scrpts in fragments, we recommend you download the sample files to see the script in its entirety.