One of my assignments recently involved uploading some data to the web. It consists of repo auctions carried by Central Bank of the Republic of Turkey. The data reside inside a big html table. That table is inside an xml file, which is served by IBM Web Content Manager.
A Brief History
The current insertion procedure is not user-friendly for the time being. They get the new repo auction results from the database by mens of an Excel VBA application. Each value in the record is placed under successive column cells. Later, they copy those rows and paste them inside a draft. As the last step, they approve it. This makes the data to be published.
Our users wanted this process to be simplified. The web content team offered the possibility of replacing the xml file with the Excel itself. By this, the users can upload the same file where the VBA application runs and carries the auction data. What I need to do was simply create a copy of the Excel file while striping off the VBA modules. Simple, right? Yet, our users objected to that idea. Their reasoning was that, since Excel is completely open to their editing, there may be some human errors in the data. The Excel file can be edited either intentionally or unintentionally. No matter what, this is unacceptable.
The web content team also has some concerns. They argued that this method of maintaining the big html table will be unsustainable when it hit a certain number of characters in it. Also, it will most probably be impossible to upload the whole html table each time.
With all those in my mind, I thought that there must be a way to have the best of both worlds. To achieve that, I need to somehow update the html file by appending the new auctions at the very end of the html table. This should satisfy the user, since I will never use the Excel file again. Also, I won’t change the values of the previous auctions, which is a strict requirement of the users. Moreover, I will have the option to trim the very old auctions by deleting the first rows of the html table. That is welcome by the web content team. So how shall I start?
The Common Ground
Let’s think about the steps of the new process:
1. Connect to the server
2. Request1: Create a draft with the current web page
3. Update the html table in the web page by appending the new auctions
4. Request2: Put the modified web page back to server
5. Request3: Send the draft to approval
After setting these steps, web content team send me an example code showing how to connect to our IBM WCM server, make requests and get the responses. I also found the necessary documentation and examples detailing these steps.
Since I will be making XML and HTML parsing, I chose jsoup, which can parse both of these, as my primary library. Now I was ready to start to code a small Java client application. At least I thought so.
The first thing about the IBM WCM is that, every document has a UUID. The web content team provided me the UUID of the document I was dealing with. I though that, I would use this for all my three requests. However, the mechanism is not like this. In create draft (Request1), we need to use the UUID of the document, that’s ok. But, the draft creation request generates a response which contains another id representing that draft. This is because there may be more than one draft for the same document at the same time frame. We extract that id by parsing the xml response and use it in the following put (Request2) and send approval (Request3) requests. It’s critical.
package tr.gov.tcmb.pgm.api.network; import java.io.IOException; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.AuthCache; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.LaxRedirectStrategy; import org.apache.log4j.Logger; import tr.gov.tcmb.pgm.api.model.DraftWrapper; /** * * @author Merter Sualp * This class is responsible for making the network operations */ public class NetworkOperations { private static final String HOST = "idmvwaut1.tcmb.gov.tr"; private static final int PORT = 80; // can be replaced by 443 private static final String PROTOCOL = "http" // can be replaced by "https" private static final String USERNAME = "user_name"; private static final String PASSWORD = "password"; private static final String TEXT_CONTENT = "text/plain"; private static final String XML_CONTENT = "application/atom+xml"; public static final String BASE_URI = PROTOCOL + "://" + HOST; public static final String BASE_IYS_URL = BASE_URI + "/wps/mycontenthandler/wcmrest"; public static final String CREATE_DRAFT_TEMPLATE = "%sitem/%s/create-draft"; public static final String CONTENT_TEMPLATE = "%sContent/%s"; public static final String APPROVE_TEMPLATE = "%sitem/%s/next-stage"; private static final Logger logger = Logger.getLogger(NetworkOperations.class); private enum WcmConnection { SINGLETON_CONNECTION(); private final CloseableHttpClient httpClient; private final HttpClientContext context; private final HttpHost targetHost; private WcmConnection() { httpClient = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build(); targetHost = new HttpHost(HOST, PORT, PROTOCOL); CredentialsProvider credsProvider = new BasicCredentialsProvider(); credsProvider.setCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()), new UsernamePasswordCredentials(USERNAME, PASSWORD)); AuthCache authCache = new BasicAuthCache(); BasicScheme basicAuth = new BasicScheme(); authCache.put(targetHost, basicAuth); context = HttpClientContext.create(); context.setCredentialsProvider(credsProvider); context.setAuthCache(authCache); } private CloseableHttpResponse execute(HttpEntityEnclosingRequestBase request) throws ClientProtocolException, IOException { CloseableHttpResponse response = httpClient.execute(targetHost, request, context); logger.trace(response.getStatusLine()); return response; } } /** * This method prepares the request for creating a draft document. * The given UUID is used as the template document for this draft. * * @param docUuid * @return CloseableHttpResponse * @throws IOException */ public static CloseableHttpResponse createDraft(String docUuid) throws IOException { HttpPost draftCreateRequest = new HttpPost(String.format(CREATE_DRAFT_TEMPLATE, BASE_IYS_URL, docUuid)); setHeaderInRequest(draftCreateRequest, TEXT_CONTENT); return getResponseForRequest(draftCreateRequest); } private static void setHeaderInRequest(HttpEntityEnclosingRequestBase request, String content) { request.setHeader("Content-Type", content); } private static CloseableHttpResponse getResponseForRequest(HttpEntityEnclosingRequestBase request) throws ClientProtocolException, IOException { CloseableHttpResponse response = WcmConnection.SINGLETON_CONNECTION .execute(request); checkResponse(response); return response; } private static void checkResponse(CloseableHttpResponse response) throws FailedRequestException { if (response.getStatusLine().getStatusCode() == 200) return; if (response.getStatusLine().getStatusCode() == 201) return; throw new FailedRequestException(); } }
To create a draft for a given UUID, calling the createDraft(String docUuid) method above is enough. It forms the url string which will be posted and sets the header information accordingly. After that, it send this request and gets the response. While doing it, I preferred to implement an enumeration for making the singleton connection simpler. The very first call for the execute method of the WcmConnection enumeration creates the single object. The following requests will not do that again. They will use the exact connection object which was initialized before draft creation.
private static Document getXmlDocument(CloseableHttpResponse response) throws IOException, SAXException, ParserConfigurationException { HttpEntity entity = response.getEntity(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document xmlDoc = builder.parse(entity.getContent()); xmlDoc.getDocumentElement().normalize(); return xmlDoc; }
My initial plan was to employ jsoup for both parsing the wrapper xml and its html child, which contains the table and is a simple character data. Below is an example of it.
<data type="text/HTML><![CDATA[ ... HTML tags... ]]></data>
The problem we encountered was that, the XML parser of jsoup turns all tags into lowercase counterparts. That is a serious issue. We constantly got 400 as response status code. I immediately changed my approach and employed a standard DOM parser for XML. jsoup was only used for HTML character data.
private static String extractDraftIdFrom(Document xmlDoc) throws TagNonExistentException { Node idElement = extractTagStartingFrom(DRAFT_ID_TAG, xmlDoc.getDocumentElement()); if (idElement == null) throw new TagNonExistentException(); return idElement.getTextContent().substring(PRE_ID_KEY.length()); } private static Node extractTagStartingFrom(String tag, Node parent) throws TagNonExistentException { Node requiredElement = null; NodeList children = parent.getChildNodes(); for (int i=0; i<children.getLength(); i++) { requiredElement = children.item(i); if (tag.equals(requiredElement.getNodeName())) { break; } requiredElement = null; } if (requiredElement == null) throw new TagNonExistentException(); return requiredElement; }
The next step after parsing the XML file is extracting the id assigned for the draft. The child contains this information prepended with “wcmrest:” string. We get the remaining of the text value as the id. The extractTagStartingFrom(String tag, Node parent) may seem overkill here. As you will see, we will use this method to find other nodes inside the XML document.
private static Node extractTableXmlElement(Document xmlDoc) throws TagNonExistentException { Node contentNode = extractTagStartingFrom("content", xmlDoc.getDocumentElement()); Node wcmContentNode = extractTagStartingFrom("wcm:content", contentNode); Node elementsNode = extractTagStartingFrom("elements", wcmContentNode); Node bodyElementNode = extractTagHavingAttributeValueStartingFrom( "element", "name", "Body", elementsNode); Node dataNode = extractTagStartingFrom("data", bodyElementNode); return dataNode.getFirstChild(); } private static Node extractTagHavingAttributeValueStartingFrom(String tag, String attr, String value, Node parent) throws TagNonExistentException { Node requiredElement; NodeList children = parent.getChildNodes(); for (int i=0; i<children.getLength(); i++) { requiredElement = children.item(i); if (tag.equals(requiredElement.getNodeName()) && doesNodeContainAttributeHavingValue(attr, value, requiredElement)) return requiredElement; } throw new TagNonExistentException(); } private static boolean doesNodeContainAttributeHavingValue(String attr, String value, Node requiredElement) { NamedNodeMap attributes = requiredElement.getAttributes(); for (int a=0; a<attributes.getLength(); a++) { Node theAttribute = attributes.item(a); if (theAttribute.getNodeName().equals(attr) && theAttribute.getNodeValue().equals(value)) return true; } return false; }
After getting the draft id, what we will do is to find the XML part which holds the HTML table. The path to it starts with the “content” child of the root. That node has a “wcm:content” child. There are “elements” as children of “wcm:content”. One of the children has an attribute, “name” and that attribute has a value “Body”. At that point, we get a child node, “data”, which is the parent of the HTML table as a character data. Here you can see that we are extensively calling the extractTagStartingFrom(String tag, Node parent) method to reach the destination. Moreover, at one place, we are calling extractTagHavingAttributeValueStartingFrom(String tag, String attr, String value, Node parent) to find the node that we are interested in.
private static String addAuction(Node oldTableXmlElement) { org.jsoup.nodes.Document htmlDoc = extractHtmlTableFrom( oldTableXmlElement); org.jsoup.nodes.Element allAuctions = htmlDoc.getElementsByTag("tbody") .first(); org.jsoup.nodes.Element newAuction = new org.jsoup.nodes.Element( Tag.valueOf("tr"), NetworkOperations.BASE_URI); List data = new LinkedList(); data.add("28.06.2016"); data.add("MİKTAR"); data.add("28.06.2016"); data.add("12.07.2016"); data.add("14"); data.add("21,765,467.46"); data.add("10,999,999.97"); data.add("7.50"); data.add("7.50"); data.add("7.50"); data.add("7.78"); data.add("7.78"); data.add("7.78"); for (String datum : data) { org.jsoup.nodes.Element tableElement = new org.jsoup.nodes.Element( Tag.valueOf("td"), NetworkOperations.BASE_URI); tableElement.appendText(datum); newAuction.appendChild(tableElement); } allAuctions.appendChild(newAuction); return htmlDoc.body().html(); } private static org.jsoup.nodes.Document extractHtmlTableFrom( Node oldTableXmlElement) { String htmlContent = oldTableXmlElement.getTextContent(); return Jsoup.parse(htmlContent); }
Up until now, our only aim was to reach where the HTML table is. So, we did not deal with jsoup in anywhere. That is intentional. If, for any reason, we would like to change the XML format, or XML parsing libraries, or HTML format or HTML parsing libraries, these abstraction layers should obstruct help our cause. The other parts of the code will be left untouched. For example, the codes above are the only ones where jsoup calls are made and no other prior stuff is involved. We pare the HTML, create and append a new table row to the existing table, and return it as a String. Simple.
private static String modifyXmlDocAsString(String newHtmlDocAsString, Node oldTableXmlElement, Document xmlDoc) throws XmlToStringConversionException { CDATASection cdata = xmlDoc.createCDATASection(newHtmlDocAsString); oldTableXmlElement.getParentNode().replaceChild(cdata, oldTableXmlElement); return xmlToString(xmlDoc); } private static String xmlToString(Document doc) throws XmlToStringConversionException { try { StringWriter sw = new StringWriter(); TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.transform(new DOMSource(doc), new StreamResult(sw)); return sw.toString(); } catch (Exception ex) { logger.error(ex.getMessage()); throw new XmlToStringConversionException( "Error converting to String: " + ex.getMessage()); } }
So far, we were able to the new HTML table. Now, we will get that, create a character data node and put it back to the main XML file. I used CDATASection interface for it. By means of the XML document, it can be created. I also replaced the old table with this one. After that, I wanted the full text form of the new XML. Interestingly, as far as I know, there is no single method to accomplish that. Hence, I got this elegant java code to do that.
private static DraftWrapper addMissingAuctions() throws IOException, SAXException, ParserConfigurationException, FailedRequestException, TagNonExistentException, XmlToStringConversionException { CloseableHttpResponse response = null; try { response = NetworkOperations.createDraft(DraftWrapper.CONTENT_UUID); Document xmlDoc = getXmlDocument(response); String draftId = extractDraftIdFrom(xmlDoc); Node oldTableXmlElement = extractTableXmlElement(xmlDoc); String newHtmlDocAsString = addAuction(oldTableXmlElement); String xmlDocumentAsText = modifyXmlDocAsString(newHtmlDocAsString, oldTableXmlElement, xmlDoc); logger.trace(xmlDocumentAsText); return DraftWrapper.valueOf( new ByteArrayEntity(xmlDocumentAsText.getBytes("UTF-8")), draftId); } catch (IOException ex) { logger.error(ex.getMessage()); throw ex; } catch (UnsupportedOperationException e) { logger.error(e.getMessage()); throw e; } catch (FailedRequestException e) { logger.error(e.getMessage()); throw e; } catch (TagNonExistentException e) { logger.error(e.getMessage()); throw e; } catch (XmlToStringConversionException e) { logger.error(e.getMessage()); throw e; } finally { if (response != null) response.close(); } }
The last step is to convert the string/text form of XML file into an http entity. A ByteArrayEntity() is just fine. I show above the whole code for appending new auctions to the existing ones. You see that I return a DraftWrapper object.
package tr.gov.tcmb.pgm.api.model; import org.apache.http.HttpEntity; /** * This class contains the id and http entity data which are used in IBM wcm * requests * * @author asgmsta * */ public class DraftWrapper { public static final String CONTENT_UUID = "D0CUM3N7-C0N73NT-1D; private final HttpEntity body; private final String draftId; private DraftWrapper(HttpEntity body, String draftId) { this.body = body; this.draftId = draftId; } public HttpEntity getBody() { return body; } public String getDraftId() { return draftId; } /** * The static factory method creating a DraftWrapper * * @param body * @param draftId * @return DraftWrapper */ public static DraftWrapper valueOf(HttpEntity body, String draftId) { return new DraftWrapper(body, draftId); } }
After modifying the draft, our next steps require the draft id and the entity. I did not want to make a stateful object, therefore I did not add these as private fields of any class. The result is creating a DraftWrapper object. The only stateful class is this one. The wcm and network operation objects are all stateless.
private static void updateServerDocumentWith(DraftWrapper draftWrapper) throws IOException, FailedRequestException { CloseableHttpResponse response = null; try { response = NetworkOperations.putModifiedDraft(draftWrapper); } catch (IOException ex) { logger.error(ex.getMessage()); throw ex; } finally { if (response != null) response.close(); } } /** * This method prepares the request for putting modified draft back to the * server The given DraftWrapper contains the id of the draft and the bytes * of the draft itself to be put. * * @param draftWrapper * @return CloseableHttpResponse * @throws IOException */ public static CloseableHttpResponse putModifiedDraft( DraftWrapper draftWrapper) throws IOException, FailedRequestException { HttpPut putRequest = new HttpPut(String.format(CONTENT_TEMPLATE, BASE_IYS_URL, draftWrapper.getDraftId())); putRequest.setEntity(draftWrapper.getBody()); setHeaderInRequest(putRequest, XML_CONTENT); return getResponseForRequest(putRequest); }
We are getting really close finish. Our draft is ready and now it is time to put it back to the wcm server. We initialize a put request and set its body as the entity body we previously put inside the DraftWrapper. The important distinction here is this entity addition and setting the content as “application/atom+xml”. All our other requests are post and their content type is “tetx/plain”.
private static void sendToApproval(String draftId) throws IOException, FailedRequestException { CloseableHttpResponse response = null; try { response = NetworkOperations.sendToApproval(draftId); } catch (IOException ex) { logger.error(ex.getMessage()); throw ex; } finally { if (response != null) response.close(); } } /** * This method changes the state of the created and modified draft into * ready for approval. * * @param docUuid * @return CloseableHttpResponse * @throws IOException */ public static CloseableHttpResponse sendToApproval(String docUuid) throws IOException, FailedRequestException { HttpPost approvalRequest = new HttpPost( String.format(APPROVE_TEMPLATE, BASE_IYS_URL, docUuid)); setHeaderInRequest(approvalRequest, TEXT_CONTENT); return getResponseForRequest(approvalRequest); }
The last step is sending the newly modified draft, which is on the server now, to approval. It only needs the draft id. Nothing more. The whole WcmOperation.java code is below for the sake of completeness.
package tr.gov.tcmb.pgm.api.wcm; import java.io.IOException; import java.io.StringWriter; import java.util.LinkedList; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.entity.ByteArrayEntity; import org.apache.log4j.Logger; import org.jsoup.Jsoup; import org.jsoup.parser.Tag; import org.w3c.dom.CDATASection; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import tr.gov.tcmb.pgm.api.exceptions.FailedRequestException; import tr.gov.tcmb.pgm.api.exceptions.TagNonExistentException; import tr.gov.tcmb.pgm.api.exceptions.XmlToStringConversionException; import tr.gov.tcmb.pgm.api.model.DraftWrapper; import tr.gov.tcmb.pgm.api.network.NetworkOperations; public class WcmOperation { private static final Logger logger = Logger.getLogger(WcmOperation.class); private static final String PRE_ID_KEY = "wcmrest:"; private static final String DRAFT_ID_TAG = "id"; private WcmOperation() { } public static void main(String[] args) { try { DraftWrapper draftWrapper = addMissingAuctions(); updateServerDocumentWith(draftWrapper); sendToApproval(draftWrapper.getDraftId()); } catch (IOException ex) { StringBuilder stb = new StringBuilder(); stb.append("There is an error because of this: "); stb.append(ex.getCause()); stb.append(ex.getMessage()); logger.error(stb.toString()); } catch (SAXException e) { logger.error(e.getMessage()); } catch (ParserConfigurationException e) { logger.error(e.getMessage()); } catch (TagNonExistentException e) { logger.error(e.getMessage()); } catch (FailedRequestException e) { logger.error(e.getMessage()); } catch (XmlToStringConversionException e) { logger.error(e.getMessage()); } } private static DraftWrapper addMissingAuctions() throws IOException, SAXException, ParserConfigurationException, FailedRequestException, TagNonExistentException, XmlToStringConversionException { CloseableHttpResponse response = null; try { response = NetworkOperations.createDraft(DraftWrapper.CONTENT_UUID); Document xmlDoc = getXmlDocument(response); String draftId = extractDraftIdFrom(xmlDoc); Node oldTableXmlElement = extractTableXmlElement(xmlDoc); String newHtmlDocAsString = addAuction(oldTableXmlElement); String xmlDocumentAsText = modifyXmlDocAsString(newHtmlDocAsString, oldTableXmlElement, xmlDoc); logger.trace(xmlDocumentAsText); return DraftWrapper.valueOf( new ByteArrayEntity(xmlDocumentAsText.getBytes("UTF-8")), draftId); } catch (IOException ex) { logger.error(ex.getMessage()); throw ex; } catch (UnsupportedOperationException e) { logger.error(e.getMessage()); throw e; } catch (FailedRequestException e) { logger.error(e.getMessage()); throw e; } catch (TagNonExistentException e) { logger.error(e.getMessage()); throw e; } catch (XmlToStringConversionException e) { logger.error(e.getMessage()); throw e; } finally { if (response != null) response.close(); } } private static Document getXmlDocument(CloseableHttpResponse response) throws IOException, SAXException, ParserConfigurationException { HttpEntity entity = response.getEntity(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document xmlDoc = builder.parse(entity.getContent()); xmlDoc.getDocumentElement().normalize(); return xmlDoc; } private static String extractDraftIdFrom(Document xmlDoc) throws TagNonExistentException { Node idElement = extractTagStartingFrom(DRAFT_ID_TAG, xmlDoc.getDocumentElement()); if (idElement == null) throw new TagNonExistentException(); return idElement.getTextContent().substring(PRE_ID_KEY.length()); } private static Node extractTagStartingFrom(String tag, Node parent) throws TagNonExistentException { Node requiredElement = null; NodeList children = parent.getChildNodes(); for (int i=0; i<children.getLength(); i++) { requiredElement = children.item(i); if (tag.equals(requiredElement.getNodeName())) { break; } requiredElement = null; } if (requiredElement == null) throw new TagNonExistentException(); return requiredElement; } private static Node extractTableXmlElement(Document xmlDoc) throws TagNonExistentException { Node contentNode = extractTagStartingFrom("content", xmlDoc.getDocumentElement()); Node wcmContentNode = extractTagStartingFrom("wcm:content", contentNode); Node elementsNode = extractTagStartingFrom("elements", wcmContentNode); Node bodyElementNode = extractTagHavingAttributeValueStartingFrom( "element", "name", "Body", elementsNode); Node dataNode = extractTagStartingFrom("data", bodyElementNode); return dataNode.getFirstChild(); } private static Node extractTagHavingAttributeValueStartingFrom(String tag, String attr, String value, Node parent) throws TagNonExistentException { Node requiredElement; NodeList children = parent.getChildNodes(); for (int i=0; i<children.getLength(); i++) { requiredElement = children.item(i); if (tag.equals(requiredElement.getNodeName()) && doesNodeContainAttributeHavingValue(attr, value, requiredElement)) return requiredElement; } throw new TagNonExistentException(); } private static boolean doesNodeContainAttributeHavingValue(String attr, String value, Node requiredElement) { NamedNodeMap attributes = requiredElement.getAttributes(); for (int a=0; a<attributes.getLength(); a++) { Node theAttribute = attributes.item(a); if (theAttribute.getNodeName().equals(attr) && theAttribute.getNodeValue().equals(value)) return true; } return false; } private static String addAuction(Node oldTableXmlElement) { org.jsoup.nodes.Document htmlDoc = extractHtmlTableFrom( oldTableXmlElement); org.jsoup.nodes.Element allAuctions = htmlDoc.getElementsByTag("tbody") .first(); org.jsoup.nodes.Element newAuction = new org.jsoup.nodes.Element( Tag.valueOf("tr"), NetworkOperations.BASE_URI); List data = new LinkedList(); data.add("28.06.2016"); data.add("MİKTAR"); data.add("28.06.2016"); data.add("12.07.2016"); data.add("14"); data.add("21,765,467.46"); data.add("10,999,999.97"); data.add("7.50"); data.add("7.50"); data.add("7.78"); data.add("7.78"); data.add("7.78"); for (String datum : data) { org.jsoup.nodes.Element tableElement = new org.jsoup.nodes.Element( Tag.valueOf("td"), NetworkOperations.BASE_URI); tableElement.appendText(datum); newAuction.appendChild(tableElement); } allAuctions.appendChild(newAuction); return htmlDoc.body().html(); } private static org.jsoup.nodes.Document extractHtmlTableFrom( Node oldTableXmlElement) { String htmlContent = oldTableXmlElement.getTextContent(); return Jsoup.parse(htmlContent); } private static String modifyXmlDocAsString(String newHtmlDocAsString, Node oldTableXmlElement, Document xmlDoc) throws XmlToStringConversionException { CDATASection cdata = xmlDoc.createCDATASection(newHtmlDocAsString); oldTableXmlElement.getParentNode().replaceChild(cdata, oldTableXmlElement); return xmlToString(xmlDoc); } private static String xmlToString(Document doc) throws XmlToStringConversionException { try { StringWriter sw = new StringWriter(); TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.transform(new DOMSource(doc), new StreamResult(sw)); return sw.toString(); } catch (Exception ex) { logger.error(ex.getMessage()); throw new XmlToStringConversionException( "Error converting to String: " + ex.getMessage()); } } private static void updateServerDocumentWith(DraftWrapper draftWrapper) throws IOException, FailedRequestException { CloseableHttpResponse response = null; try { response = NetworkOperations.putModifiedDraft(draftWrapper); } catch (IOException ex) { logger.error(ex.getMessage()); throw ex; } finally { if (response != null) response.close(); } } private static void sendToApproval(String draftId) throws IOException, FailedRequestException { CloseableHttpResponse response = null; try { response = NetworkOperations.sendToApproval(draftId); } catch (IOException ex) { logger.error(ex.getMessage()); throw ex; } finally { if (response != null) response.close(); } } }