function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Dchris222Dchris222 

PDF generation failed. Check the page markup is valid.

I have a VF page that is being populated via a standard controller and a extension. It works just fine in the sandbox, but in production I am recieving the following error: " PDF generation failed. Check the page markup is valid. "

 

Any input on why this is happing would be appreciated:

 

edit: I have determined it has to do with the public case comments rendered. It works if I just have the comment body rendered as public, but if I add the created date as well then the error arises.

 

Page:

 

<apex:page renderas="pdf" standardController="Case" extensions="PDFServiceExtension">
<apex:stylesheet value="{!$Resource.PDFHeaderFooter}"/>
<center>
    <apex:image url="{!$Resource.MIRLetterhead}" width="700" height="97" />
</center>
    <apex:panelGrid columns="2" id="theGrid">
        <apex:outputLabel value="Case #: " for="Case#" style="font-weight: bold"/>
        <apex:outputField value="{!Case.CaseNumber}" id="CaNumber"/>
        
        <apex:outputLabel value="Date & Time Opened: " for="CaseOpen" style="font-weight: bold"/>
        <apex:outputField value="{!Case.CreatedDate}" id="CaseOpen"/>
        
        <apex:outputLabel value="Date & Time Closed: " for="CaseClosed" style="font-weight: bold"/>
        <apex:outputField value="{!Case.ClosedDate}" id="CaseClosed"/>
        
        <apex:outputLabel value="Status:" for="CaseStatus: " style="font-weight: bold"/>
        <apex:outputField value="{!Case.Status}" id="CaseStatus"/>
        
        <apex:outputLabel value="Type:" for="CaseType: " style="font-weight: bold"/>
        <apex:outputField value="{!Case.Type}" id="CaseType"/>
        
        <apex:outputLabel value="Account: " for="Name" style="font-weight: bold"/>
        <apex:outputField value="{!Case.Account.name}" id="Name"/>
        
        <apex:outputLabel value="Subject: " for="Case#" style="font-weight: bold"/>
        <apex:outputField value="{!Case.Subject}" id="CaSubj"/>
        
        <apex:outputLabel value="Description: " for="Case#" style="font-weight: bold"/>
        <apex:outputField value="{!Case.Description}" id="CaDesc"/>

    </apex:panelGrid>
    
    <br></br>
    <apex:outputLabel value="Service Activities:" for="Name" style="font-weight: bold"/>
    <apex:dataTable value="{!SA}" var="LIs" width="50%" border="1px" >
		<apex:column headerValue="Service Engineer" value="{!LIs.Contact__c}" headerClass="tableHead"/>
		<apex:column headerValue="Total Hours" value="{!LIs.Total_Hours__c}"/>
		<apex:column headerValue="Date Performed" value="{!LIs.Date_Performed__c}"/>
	</apex:dataTable>
	
	<br></br>
    <apex:outputLabel value="Case Comments:" for="Name2" style="font-weight: bold"/> 

    <apex:pageBlock >
        <apex:pageBlockTable value="{!case.casecomments}" var="c" width="50%" border="1px" >
            <apex:column value="{!c.commentbody}" rendered="{!c.isPublished = true}"/>
            <apex:column value="{!c.createddate}" rendered="{!c.isPublished = true}"/>
        </apex:pageBlockTable>
    </apex:pageBlock>   
</apex:page>

 Extension:

public with sharing class PDFServiceExtension {
	
	private final Case Ca;
  
	public PDFServiceExtension(ApexPages.StandardController CaseController) {
		this.Ca = (Case)CaseController.getRecord();
	}
  
	//For Testing Purposes  
	public PDFServiceExtension(Case tcase) {
        this.Ca = tcase;
	}
  
  // Create a list of type Suite__c and call the query method
    public List<Service_Activity__c> activityRecords() {
         return (List<Service_Activity__c>) activityRecords.getRecords();
    }
    
  // Query the db for the list of Suite__c records to use on the vf page and return a list
    public ApexPages.StandardSetController activityRecords{
      get {
            if(activityRecords == null) {
                activityRecords = new ApexPages.StandardSetController(Database.getQueryLocator([Select s.Contact__c, s.Case__c, s.Total_Hours__c, s.Description__c, s.Date_Performed__c  from Service_Activity__c s Where s.Case__c =:ApexPages.currentPage().getParameters().get('id')]));
            }
            return activityRecords;
        }
        set;
    }
    
    public List<Service_Activity__c> getSA() {
        return (List<Service_Activity__c>) activityRecords.getRecords();
    }
}

 

jwetzlerjwetzler

Surprised we haven't run into this before. It looks like whenever you have a dataTable or pageBlockTable that gets rendered, but none of the columns get rendered based on your criteria (essentially, an empty table), it generates some HTML that the PDF renderer does not like. At least, that's what I'm experiencing (not sure the problem is really with createdDate, I can reproduce it with the comment body as well, as long as I have no published case comments on that particular case).

 

I'll log a bug internally but you should be able to workaround it by putting a rendered attribute on your pageBlockTable (or maybe even your pageBlock? Depends on what you want the UI to look like) that will only display the table if your case has published comments.

 

So add this to your pageBlock or pageBlockTable: rendered="{!publishedComments > 0}"

And something like this in your extension constructor (with a getter for your publishedComments integer):

publishedComments = [select count() from CaseComment where parentid = :Ca.id and isPublished = true];

 

Sorry about the workaround but hopefully it will unblock you.

colemabcolemab

@jwetzler I have seen this before but not in the data table directly - when using apex:repeat to generate a table.   Most browsers will accept a blank table, or blank TRs or missing TD's but adobe / pdf will not.

 

In my experience, the best thing to do when you get the generic PDF generation failed error is to render the page into HTML and then run the HTML thru a parser / validator to locate the error(s) and fix them.

 

You simply must have valid (to the PDF engine anyway) HTML to generate.  Leaving a tag open can cause a failure depending on the tag.

jwetzlerjwetzler

Yeah. In this case all the tags are balanced. I think the problem is the <colgroup> tag that gets generated, but the table has no columns. The PDF renderer gets a little finicky with table structure.

Dchris222Dchris222

JWetzler,

 

I added the following to my extension, but I am still recieving the same error message:

 

The particular case has two public comments and 1 private and the order in which they are public or private seems to determine if it fails or not.

 

 

Extension:

public integer getpublishedComments() {
    	return [select count() from CaseComment where parentid = :Ca.id and isPublished = true];
    
    }

 Page:

<apex:pageBlock rendered="{!publishedComments > 0}">
        <apex:pageBlockTable value="{!case.casecomments}" var="c" width="50%" border="1px" >
            <apex:column value="{!c.commentbody}" rendered="{!c.isPublished = true}"/>
            <apex:column value="{!c.createddate}" rendered="{!c.isPublished = true}"/>
        </apex:pageBlockTable>
    </apex:pageBlock>   
</apex:page>

 


jwetzlerjwetzler

Bummer. Okay then it must be a more general problem with columns. I'll see if I can put more detail in the bug but you should be able to get around it by querying for only published CaseComments and then using the dataTable with that list, rendering your pageblock only if the list isn't empty. Let me know if that works for you.

jwetzlerjwetzler

And yeah it seems that if the very last comment is not public it fails. If the first or second are public it works.

 

But not having unpublished comments in the collection in the first place should work for you.

Dchris222Dchris222

Ok, so I created another extension and then tied the data table to it, but I am now geting this error: "List controllers are not supported for CaseComment".

 

One problem to another, any guidence on this?

 

Much appreciated

 

Extension2

public with sharing class PDFServiceExtension2 {
	
	private final Case Ca;
  
  public PDFServiceExtension2(ApexPages.StandardController CaseController) {
    this.Ca = (Case)CaseController.getRecord();
  }
  
  // Create a list of type CaseComment and call the query method
    public List<CaseComment> commentRecords() {
         return (List<CaseComment>) commentRecords.getRecords();
    }
    
  // Query the db for the list of CaseComment records to use on the vf page and return a list
    public ApexPages.StandardSetController commentRecords{
      get {
            if(commentRecords == null) {
                commentRecords = new ApexPages.StandardSetController(Database.getQueryLocator([Select CommentBody, CreatedDate, IsPublished, ParentId  from CaseComment  Where IsPublished = true AND ParentId  =:ApexPages.currentPage().getParameters().get('id')]));
            }
            return commentRecords;
        }
        set;
    }
    
    public List<CaseComment> getCC() {
        return (List<CaseComment>) commentRecords.getRecords();
    }
}

Page:

<apex:page renderas="pdf" standardController="Case" extensions="PDFServiceExtension,PDFServiceExtension2">
<apex:stylesheet value="{!$Resource.PDFHeaderFooter}"/>
<center>
    <apex:image url="{!$Resource.MIRLetterhead}" width="700" height="97" />
</center>
    <apex:panelGrid columns="2" id="theGrid">
        <apex:outputLabel value="Case #: " for="Case#" style="font-weight: bold"/>
        <apex:outputField value="{!Case.CaseNumber}" id="CaNumber"/>
        
        <apex:outputLabel value="Date & Time Opened: " for="CaseOpen" style="font-weight: bold"/>
        <apex:outputField value="{!Case.CreatedDate}" id="CaseOpen"/>
        
        <apex:outputLabel value="Date & Time Closed: " for="CaseClosed" style="font-weight: bold"/>
        <apex:outputField value="{!Case.ClosedDate}" id="CaseClosed"/>
        
        <apex:outputLabel value="Status:" for="CaseStatus: " style="font-weight: bold"/>
        <apex:outputField value="{!Case.Status}" id="CaseStatus"/>
        
        <apex:outputLabel value="Type:" for="CaseType: " style="font-weight: bold"/>
        <apex:outputField value="{!Case.Type}" id="CaseType"/>
        
        <apex:outputLabel value="Account: " for="Name" style="font-weight: bold"/>
        <apex:outputField value="{!Case.Account.name}" id="Name"/>
        
        <apex:outputLabel value="Subject: " for="Case#" style="font-weight: bold"/>
        <apex:outputField value="{!Case.Subject}" id="CaSubj"/>
        
        <apex:outputLabel value="Description: " for="Case#" style="font-weight: bold"/>
        <apex:outputField value="{!Case.Description}" id="CaDesc"/>

    </apex:panelGrid>
    
    <br></br>
    <apex:outputLabel value="Service Activities:" for="Name" style="font-weight: bold"/>
    <apex:dataTable value="{!SA}" var="LIs" width="50%" border="1px" >
		<apex:column headerValue="Service Engineer" value="{!LIs.Contact__c}" headerClass="tableHead"/>
		<apex:column headerValue="Total Hours" value="{!LIs.Total_Hours__c}"/>
		<apex:column headerValue="Date Performed" value="{!LIs.Date_Performed__c}"/>
	</apex:dataTable>
	
	<br></br>
    <apex:outputLabel value="Case Comments:" for="Name2" style="font-weight: bold"/> 
<apex:dataTable value="{!CC}" var="CCs" width="50%" border="1px" >
  <apex:column headerValue="Created Date:" value="{!CCs.createddate}" headerClass="tableHead"/>
<apex:column headerValue="Comment:" value="{!CCs.commentBody}"/>
</apex:dataTable>
</apex:page>

 



 


jwetzlerjwetzler

That all seems like overkill. Why are you trying to instantiate a StandardSetController? You shouldn't even need another extension. Just

public with sharing class PDFServiceExtension {
	
	private final Case Ca;
 public List<CaseComment> comments {get;set;}   
	public PDFServiceExtension(ApexPages.StandardController CaseController) {
		this.Ca = (Case)CaseController.getRecord();
 comments = [select commentbody, createddate from CaseComment where parentid = :ca.id and ispublished = true]; 	}
  
	//For Testing Purposes  
	public PDFServiceExtension(Case tcase) {
        this.Ca = tcase;
	}
  
  // Create a list of type Suite__c and call the query method
    public List<Service_Activity__c> activityRecords() {
         return (List<Service_Activity__c>) activityRecords.getRecords();
    }
    
  // Query the db for the list of Suite__c records to use on the vf page and return a list
    public ApexPages.StandardSetController activityRecords{
      get {
            if(activityRecords == null) {
                activityRecords = new ApexPages.StandardSetController(Database.getQueryLocator([Select s.Contact__c, s.Case__c, s.Total_Hours__c, s.Description__c, s.Date_Performed__c  from Service_Activity__c s Where s.Case__c =:ApexPages.currentPage().getParameters().get('id')]));
            }
            return activityRecords;
        }
        set;
    }
    
    public List<Service_Activity__c> getSA() {
        return (List<Service_Activity__c>) activityRecords.getRecords();
    }
}

 And then in your page:

 

<apex:pageBlock rendered="{!comments.size > 0}">
        <apex:pageBlockTable value="{!comments}" var="c" width="50%" border="1px" >
            <apex:column value="{!c.commentbody}"/>
            <apex:column value="{!c.createddate}"/>
        </apex:pageBlockTable>
    </apex:pageBlock>   
</apex:page>

 

I didn't actually save this myself so it's possible I have some compilation errors in there, but that's the general idea.